diff --git a/.github/workflows/pullrequest_check.yml b/.github/workflows/pullrequest_check.yml index 55f8ba3a1..1157ea14b 100644 --- a/.github/workflows/pullrequest_check.yml +++ b/.github/workflows/pullrequest_check.yml @@ -12,7 +12,8 @@ on: jobs: ts_and_rust_lint: - if: github.event.pull_request.draft == false + # TODO AAZ: Make sure this check works upon merging into master. + if: github.event.pull_request.draft == false && github.event.pull_request.base.ref == 'master' runs-on: ubuntu-latest steps: - name: Checkout @@ -63,7 +64,8 @@ jobs: run: yarn run check integration_and_unit_tests: - if: github.event.pull_request.draft == false + # TODO AAZ: Make sure this check works upon merging into master. + if: github.event.pull_request.draft == false && github.event.pull_request.base.ref == 'master' runs-on: ubuntu-latest steps: - name: Checkout diff --git a/README.md b/README.md index 1dd5a4723..3d3fbbee7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![](https://github.com/esrlabs/chipmunk/actions/workflows/release_next.yml/badge.svg)](https://github.com/esrlabs/chipmunk/actions/workflows/release_next.yml) [![](https://github.com/esrlabs/chipmunk/actions/workflows/lint_master.yml/badge.svg)](https://github.com/esrlabs/chipmunk/actions/workflows/lint_master.yml) -`chipmunk` is one of the fastest desktop applications for viewing log files, with no limitations on file size. 1 GB, 2 GB, 10 GB? `chipmunk` is limited only by your disk space — nothing more. With no caching and no unnecessary copying, files of any size open with the same speed. But `chipmunk` goes beyond just working with files: it also allows you to create network connections to collect logs via TCP, UDP, Serial, or from the output of a running command. +`chipmunk` is one of the fastest desktop applications for viewing log files, with no limitations on file size. 1 GB, 2 GB, 10 GB? `chipmunk` is limited only by your disk space - nothing more. With no caching and no unnecessary copying, files of any size open with the same speed. But `chipmunk` goes beyond just working with files: it also allows you to create network connections to collect logs via TCP, UDP, Serial, or from the output of a running command. ## Automotive and Network Traces @@ -19,7 +19,7 @@ Additionally, `chipmunk` allows you to work with DLT traces both as standalone f - Serial Port - Output from a command or program -For each source, you can assign a parser — for example, to collect DLT packets over a UDP connection for analysis or to save them as a standalone trace file. +For each source, you can assign a parser - for example, to collect DLT packets over a UDP connection for analysis or to save them as a standalone trace file. Another key feature is the ability to launch any command or program and collect its output, which can be analyzed in real time as it's generated. @@ -29,7 +29,7 @@ At its core, `chipmunk` is a log analysis tool. It goes beyond simple search que ![filters_create](./docs/assets/search/filters_create.gif) -The search engine works dynamically — results are updated in real time as new data is added. If you're connected to a live data source, your active filters will continuously update the search results as new logs arrive. +The search engine works dynamically - results are updated in real time as new data is added. If you're connected to a live data source, your active filters will continuously update the search results as new logs arrive. ## Metrics, Measurements, and Graphs @@ -47,6 +47,6 @@ See more details in the [documentation](https://esrlabs.github.io/chipmunk/) abo ## Contributing -We welcome contributions of all kinds — bug reports, performance improvements, documentation fixes, or new features. +We welcome contributions of all kinds - bug reports, performance improvements, documentation fixes, or new features. [Click here to view it](https://esrlabs.github.io/chipmunk/contributing/welcome/) \ No newline at end of file diff --git a/application/apps/indexer/Cargo.toml b/application/apps/indexer/Cargo.toml index fc422a3b5..d3c6cc481 100644 --- a/application/apps/indexer/Cargo.toml +++ b/application/apps/indexer/Cargo.toml @@ -7,6 +7,8 @@ members = [ "addons/file-tools", "addons/text_grep", "addons/bufread", + "descriptor", + "register", "indexer_base", "merging", "parsers", @@ -42,6 +44,9 @@ envvars = "0.1" anyhow = "1.0" toml = "0.8" blake3 = "1.8" +serialport = "4.6" +etherparse = "0.18" +indexmap = "2.10" ## Development Dependencies ## # Support for `html_reports` needs running the benchmarks via `cargo-criterion` tool. diff --git a/application/apps/indexer/addons/dlt-tools/Cargo.toml b/application/apps/indexer/addons/dlt-tools/Cargo.toml index 8c4a46e4d..09bcab017 100644 --- a/application/apps/indexer/addons/dlt-tools/Cargo.toml +++ b/application/apps/indexer/addons/dlt-tools/Cargo.toml @@ -5,11 +5,15 @@ edition = "2024" [dependencies] dlt-core.workspace = true -indexer_base = { path = "../../indexer_base" } log.workspace = true -parsers = { path = "../../parsers" } -sources = { path = "../../sources" } tokio = { workspace = true , features = ["full"] } tokio-util = { workspace = true, features = ["codec", "net"] } +indexer_base = { path = "../../indexer_base" } +parsers = { path = "../../parsers" } +sources = { path = "../../sources" } +processor = { path = "../../processor" } +stypes = { path = "../../stypes" } +session = { path = "../../session" } +uuid.workspace = true [dev-dependencies] diff --git a/application/apps/indexer/addons/dlt-tools/src/lib.rs b/application/apps/indexer/addons/dlt-tools/src/lib.rs index e07f6f515..8ca2c6c08 100644 --- a/application/apps/indexer/addons/dlt-tools/src/lib.rs +++ b/application/apps/indexer/addons/dlt-tools/src/lib.rs @@ -1,4 +1,4 @@ -#![deny(unused_crate_dependencies)] +// #![deny(unused_crate_dependencies)] // Copyright (c) 2019 E.S.R.Labs. All rights reserved. // // NOTICE: All information contained herein is, and remains @@ -10,81 +10,104 @@ // Dissemination of this information or reproduction of this material // is strictly forbidden unless prior written permission is obtained // from E.S.R.Labs. -extern crate indexer_base; - #[macro_use] extern crate log; -use dlt_core::filtering::DltFilterConfig; -use parsers::{Attachment, MessageStreamItem, ParseYield, dlt::DltParser}; -use sources::{binary::raw::BinaryByteSource, producer::MessageProducer}; +use parsers::api::*; +use processor::producer::{MessageProducer, MessageStreamItem}; +use session::session::Session; use std::{ + collections::HashMap, fs::File, - io::{BufReader, BufWriter, Write}, + io::{BufWriter, Write}, path::{Path, PathBuf}, }; +use stypes::{SessionAction, SessionSetup}; use tokio_util::sync::CancellationToken; +use uuid::Uuid; + +#[derive(Default)] +pub struct AttachmentsCollector { + pub attachments: Vec, + messages: usize, +} + +impl LogRecordsBuffer for AttachmentsCollector { + fn append(&mut self, record: LogRecordOutput<'_>) { + match record { + LogRecordOutput::Raw(..) + | LogRecordOutput::Message(..) + | LogRecordOutput::Columns(..) => { + self.messages += 1; + } + LogRecordOutput::Multiple(inner) => { + for rec in inner { + self.append(rec); + } + } + LogRecordOutput::Attachment(inner) => { + self.attachments.push(inner); + } + } + } + + async fn flush(&mut self) -> Result<(), stypes::NativeError> { + Ok(()) + } + + fn get_source_id(&self) -> u16 { + 0 + } +} pub async fn scan_dlt_ft( - input: PathBuf, - filter: Option, - with_storage_header: bool, + filename: PathBuf, + filters: Option>>, cancel: CancellationToken, ) -> Result, String> { - match File::open(input) { - Ok(input) => { - let reader = BufReader::new(&input); - let source = BinaryByteSource::new(reader); - let parser = DltParser::new( - filter.map(|f| f.into()), - None, - None, - None, - with_storage_header, - ); - - let mut producer = MessageProducer::new(parser, source); - - let mut canceled = false; - - let mut attachments = vec![]; - loop { - tokio::select! { - // Check on events in current order ensuring cancel will be checked at first - // as it's defined in the current unit tests. - biased; - _ = cancel.cancelled() => { - debug!("scan canceled"); - canceled = true; + let setup = SessionSetup { + origin: SessionAction::File(filename), + parser: parsers::dlt::descriptor::get_default_options(None, filters), + source: sources::binary::raw::get_default_options(), + }; + let (session, receiver) = Session::new(Uuid::new_v4()) + .await + .map_err(|err| err.to_string())?; + let (_, source, parser) = session + .register + .setup(&setup) + .map_err(|err| err.to_string())?; + let mut collector = AttachmentsCollector::default(); + let mut producer = MessageProducer::new(parser, source, &mut collector); + let mut canceled = false; + loop { + tokio::select! { + // Check on events in current order ensuring cancel will be checked at first + // as it's defined in the current unit tests. + biased; + _ = cancel.cancelled() => { + debug!("scan canceled"); + canceled = true; + break; + } + item = producer.read_next_segment() => { + match item { + Some((_, MessageStreamItem::Done)) | None => { break; - } - items = producer.read_next_segment() => { - match items { - Some(items) => { - for (_, item) in items { - if let MessageStreamItem::Item(ParseYield::MessageAndAttachment((_msg, attachment))) = item { - attachments.push(attachment.to_owned()); - } else if let MessageStreamItem::Item(ParseYield::Attachment(attachment)) = item { - attachments.push(attachment.to_owned()); - } - } - } - _ => { - break; - } - } - } + }, + Some(..) => { + continue; + }, } } - - if canceled { - return Ok(Vec::new()); - } - - Ok(attachments) } - Err(error) => Err(format!("failed to open file: {error}")), } + if canceled { + return Ok(Vec::new()); + } + // Keep receiver alive until here + drop(receiver); + Ok(collector.attachments) } pub fn extract_dlt_ft( @@ -126,14 +149,14 @@ mod tests { use super::*; use std::path::Path; - const DLT_FT_SAMPLE: &str = "../../../../../application/developing/resources/attachments.dlt"; + const DLT_FT_SAMPLE: &str = "../../../../developing/resources/attachments.dlt"; #[tokio::test] async fn test_scan_dlt_ft() { let input: PathBuf = Path::new(DLT_FT_SAMPLE).into(); let cancel = CancellationToken::new(); - match scan_dlt_ft(input, None, true, cancel).await { + match scan_dlt_ft(input, None, cancel).await { Ok(files) => { assert_eq!(files.len(), 3); assert_eq!("test1.txt", files.first().unwrap().name); @@ -153,7 +176,7 @@ mod tests { let cancel = CancellationToken::new(); cancel.cancel(); - match scan_dlt_ft(input, None, true, cancel).await { + match scan_dlt_ft(input, None, cancel).await { Ok(files) => { assert_eq!(files.len(), 0); } @@ -166,18 +189,10 @@ mod tests { #[tokio::test] async fn test_scan_dlt_ft_with_filter() { let input: PathBuf = Path::new(DLT_FT_SAMPLE).into(); - - let filter = DltFilterConfig { - min_log_level: None, - app_ids: None, - ecu_ids: Some(vec!["ecu2".to_string()]), - context_ids: None, - app_id_count: 0, - context_id_count: 0, - }; - + let mut filters = HashMap::new(); + filters.insert("ecu_ids".to_string(), vec!["ecu2".to_string()]); let cancel = CancellationToken::new(); - match scan_dlt_ft(input, Some(filter), true, cancel).await { + match scan_dlt_ft(input, Some(filters), cancel).await { Ok(files) => { assert_eq!(files.len(), 1); assert_eq!("test2.txt", files.first().unwrap().name); @@ -194,7 +209,7 @@ mod tests { let output = TempDir::new(); let cancel = CancellationToken::new(); - match scan_dlt_ft(input.clone(), None, true, cancel.clone()).await { + match scan_dlt_ft(input.clone(), None, cancel.clone()).await { Ok(files) => { match extract_dlt_ft( &output.dir, @@ -225,7 +240,7 @@ mod tests { let output = TempDir::new(); let cancel = CancellationToken::new(); - match scan_dlt_ft(input.clone(), None, true, cancel).await { + match scan_dlt_ft(input.clone(), None, cancel).await { Ok(files) => { let cancel = CancellationToken::new(); cancel.cancel(); @@ -248,18 +263,10 @@ mod tests { async fn test_extract_dlt_ft_with_filter() { let input: PathBuf = Path::new(DLT_FT_SAMPLE).into(); let output = TempDir::new(); - - let filter = DltFilterConfig { - min_log_level: None, - app_ids: None, - ecu_ids: Some(vec!["ecu2".to_string()]), - context_ids: None, - app_id_count: 0, - context_id_count: 0, - }; - + let mut filters = HashMap::new(); + filters.insert("ecu_ids".to_string(), vec!["ecu2".to_string()]); let cancel = CancellationToken::new(); - match scan_dlt_ft(input.clone(), Some(filter), true, cancel).await { + match scan_dlt_ft(input.clone(), Some(filters), cancel).await { Ok(files) => { let cancel = CancellationToken::new(); match extract_dlt_ft(&output.dir, FileExtractor::files_with_names(files), cancel) { @@ -285,7 +292,7 @@ mod tests { let output = TempDir::new(); let cancel = CancellationToken::new(); - match scan_dlt_ft(input.clone(), None, true, cancel).await { + match scan_dlt_ft(input.clone(), None, cancel).await { Ok(files) => { let cancel = CancellationToken::new(); match extract_dlt_ft( @@ -313,18 +320,10 @@ mod tests { async fn test_extract_dlt_ft_with_filtered_index() { let input: PathBuf = Path::new(DLT_FT_SAMPLE).into(); let output = TempDir::new(); - - let filter = DltFilterConfig { - min_log_level: None, - app_ids: None, - ecu_ids: Some(vec!["ecu2".to_string()]), - context_ids: None, - app_id_count: 0, - context_id_count: 0, - }; - + let mut filters = HashMap::new(); + filters.insert("ecu_ids".to_string(), vec!["ecu2".to_string()]); let cancel = CancellationToken::new(); - match scan_dlt_ft(input.clone(), Some(filter), true, cancel).await { + match scan_dlt_ft(input.clone(), Some(filters), cancel).await { Ok(files) => { let cancel = CancellationToken::new(); match extract_dlt_ft(&output.dir, FileExtractor::files_with_names(files), cancel) { diff --git a/application/apps/indexer/addons/file-tools/src/lib.rs b/application/apps/indexer/addons/file-tools/src/lib.rs index c4288c79f..51b39b660 100644 --- a/application/apps/indexer/addons/file-tools/src/lib.rs +++ b/application/apps/indexer/addons/file-tools/src/lib.rs @@ -13,6 +13,11 @@ pub fn is_binary(file_path: String) -> Result { Ok(from_utf8(&buffer).map_or(true, |_file_content| false)) } +pub fn is_path_binary>(file_path: P) -> Result { + let buffer = fetch_starting_chunk(file_path.as_ref())?; + Ok(from_utf8(&buffer).map_or(true, |_file_content| false)) +} + fn fetch_starting_chunk(file_path: &Path) -> Result> { let bytes_to_read: u64 = (metadata(file_path)?.len().max(1) - 1).min(BYTES_TO_READ); diff --git a/application/apps/indexer/descriptor/Cargo.toml b/application/apps/indexer/descriptor/Cargo.toml new file mode 100644 index 000000000..3b6378c4f --- /dev/null +++ b/application/apps/indexer/descriptor/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "descriptor" +version = "0.1.0" +edition = "2021" + +[dependencies] +thiserror.workspace = true +uuid = { workspace = true , features = ["serde", "v4"] } +tokio-util.workspace = true +tokio = { workspace = true , features = ["full"] } +log.workspace = true + +stypes = { path = "../stypes", features=["rustcore"] } diff --git a/application/apps/indexer/descriptor/src/lib.rs b/application/apps/indexer/descriptor/src/lib.rs new file mode 100644 index 000000000..aefbc2085 --- /dev/null +++ b/application/apps/indexer/descriptor/src/lib.rs @@ -0,0 +1,7 @@ +mod scheme; +mod tys; + +use std::collections::HashMap; + +pub use scheme::*; +pub use tys::*; diff --git a/application/apps/indexer/descriptor/src/scheme.rs b/application/apps/indexer/descriptor/src/scheme.rs new file mode 100644 index 000000000..070e7026c --- /dev/null +++ b/application/apps/indexer/descriptor/src/scheme.rs @@ -0,0 +1,299 @@ +pub use crate::*; +use tokio::select; +use tokio_util::sync::CancellationToken; +use uuid::Uuid; + +/// Represents the result of a lazy field loading operation. +/// +/// This enum is used to capture the outcome of asynchronously loading field descriptions. +/// The result can either contain the loaded fields or indicate that the operation was cancelled. +/// +/// # Variants +/// +/// * `Fields(Vec)` - A vector of static field results successfully loaded. +/// * `Cancelled` - Indicates that the lazy loading operation was cancelled before completion. +#[derive(Debug)] +pub enum LazyLoadingResult { + Fields(Vec), + Cancelled, +} + +/// Metadata associated with a lazy loading operation for field schemas. +/// +/// This struct contains information required to manage and track the process of +/// lazy loading field descriptions. It includes a unique identifier, component +/// identification, a cancellation token, and a list of field identifiers to be loaded. +/// +/// # Fields +/// +/// * `uuid` - A unique identifier for the lazy loading operation. +/// * `ident` - An identifier of the component initiating the loading process. +/// * `cancel` - A cancellation token that can be used to prematurely terminate the loading task. +/// * `fields` - A list of field identifiers that will be loaded as part of this operation. +#[derive(Debug, Clone)] +pub struct LazyLoadingTaskMeta { + /// A unique identifier for the lazy loading operation. + pub uuid: Uuid, + /// An identifier of the component initiating the loading process. + pub ident: stypes::Ident, + /// A cancellation token that allows for premature termination of the loading task. + pub cancel: CancellationToken, + /// A list of field identifiers to be loaded during the operation. + pub fields: Vec, +} + +impl LazyLoadingTaskMeta { + /// Checks if the lazy loading task contains any of the specified field identifiers. + /// + /// This method iterates over the internal list of field identifiers and checks + /// whether any of them are present in the given slice of field identifiers. + /// + /// # Arguments + /// + /// * `fields` - A slice of `String` identifiers to check against the stored field list. + /// + /// # Returns + /// + /// * `true` if at least one of the given field identifiers is found in the task's field list. + /// * `false` otherwise. + pub fn contains(&self, fields: &[String]) -> bool { + self.fields.iter().any(|id| fields.contains(id)) + } + + /// Retrieves the unique identifier (UUID) of the component owner associated with this task. + /// + /// This method returns the UUID that uniquely identifies the component + /// responsible for initiating the lazy loading operation. + /// + /// # Returns + /// + /// * `Uuid` - The unique identifier of the component owner. + pub fn owner(&self) -> Uuid { + self.ident.uuid + } +} + +/// Represents a task for lazy loading of configuration schema fields. +/// +/// This struct encapsulates metadata and an optional asynchronous task related to the lazy loading +/// of settings. The lazy loading task is used when the component requires field data that needs +/// to be fetched asynchronously. +/// +/// # Fields +/// +/// * `meta` - Metadata related to the lazy loading operation, including task ID, component ID, +/// cancellation token, and the list of fields to be loaded. +/// * `task` - An optional asynchronous task responsible for retrieving the field data. +/// The task is wrapped in `Option` to allow for cases where it may not be initialized or has +/// already completed. +pub struct LazyLoadingTask { + /// Metadata related to the lazy loading operation. + meta: LazyLoadingTaskMeta, + /// An optional asynchronous task responsible for loading the fields. + task: Option, +} + +impl LazyLoadingTask { + /// Creates a new lazy loading task for retrieving configuration schema fields. + /// + /// This method initializes the task with metadata and an asynchronous task function. + /// The task can be canceled using the provided cancellation token. + /// + /// # Arguments + /// + /// * `ident` - An identifier of the component for which the settings are being loaded. + /// * `task` - The asynchronous task responsible for loading the fields. + /// * `cancel` - A reference to a cancellation token that can interrupt the loading process. + /// * `fields` - A vector of field identifiers to be lazily loaded. + /// + /// # Returns + /// + /// * `Self` - A new instance of `LazyLoadingTask`. + pub fn new( + ident: stypes::Ident, + task: LazyFieldsTask, + cancel: &CancellationToken, + fields: Vec, + ) -> Self { + Self { + meta: LazyLoadingTaskMeta { + uuid: Uuid::new_v4(), + ident, + cancel: cancel.clone(), + fields, + }, + task: Some(task), + } + } + + /// Retrieves the metadata associated with the lazy loading task. + /// + /// This method returns a cloned copy of the metadata, which includes the task's unique ID, + /// component identifier, cancellation token, and field list. + /// + /// # Returns + /// + /// * `LazyLoadingTaskMeta` - The metadata associated with the task. + pub fn get_meta(&self) -> LazyLoadingTaskMeta { + self.meta.clone() + } + + /// Executes the lazy loading task and waits for its completion. + /// + /// This asynchronous method either completes the field loading or detects task cancellation. + /// + /// # Returns + /// + /// * `Ok(LazyLoadingResult::Fields(Vec))` - If the fields were successfully loaded. + /// * `Ok(LazyLoadingResult::Cancelled)` - If the loading operation was cancelled. + /// * `Err(stypes::NativeError)` - If there was an error during the loading process. + /// + /// # Behavior + /// + /// The method uses asynchronous selection to await either the task's completion or a cancellation signal. + /// If the cancellation token signals cancellation before the task completes, the method returns + /// `LazyLoadingResult::Cancelled`. Otherwise, it returns the loaded fields. + /// + /// # Note + /// + /// The cancellation mechanism is designed to be safe and non-blocking. + /// The decision on whether to control cancellation at the parser/source level or delegate it + /// to a higher layer is still under consideration. + pub async fn wait(&mut self) -> Result { + let Some(task) = self.task.take() else { + return Err(stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Configuration, + message: Some(format!( + "Settings already requested for {}", + self.meta.ident.name + )), + }); + }; + // TODO: cancel safe + select! { + _ = self.meta.cancel.cancelled() => { + // TODO: the question - shell we controll cancellation about parser/source, + // or we can deligate it? I think we should controll it above. + Ok(LazyLoadingResult::Cancelled) + } + fields = task => { + let fields = fields.map_err(|_| stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::ChannelError, + message: Some(format!( + "Fail to get settings for {}", + self.meta.ident.name + )), + })?; + // Check one more time in case if cancel was catched first on level of parser/source + if self.meta.cancel.is_cancelled() { + Ok(LazyLoadingResult::Cancelled) + } else { + Ok(LazyLoadingResult::Fields(fields)) + } + } + } + } +} + +/// Represents the configuration schema of a component. +/// +/// This struct defines the structure of a component's settings, including both +/// static fields and an optional lazy loading task for fields that require asynchronous loading. +/// +/// # Fields +/// +/// * `spec` - A vector containing field descriptors that define the structure and types of settings. +/// * `lazy` - An optional task that handles the lazy loading of settings fields that are not immediately available. +pub struct OptionsScheme { + /// Describes all the fields in the component's configuration. + pub spec: Vec, + /// Contains a task responsible for the "lazy" loading of specific configuration fields. + pub lazy: Option, +} + +impl OptionsScheme { + /// Creates a new `OptionsScheme` with the given field descriptors. + /// + /// This method initializes a new instance of `OptionsScheme` with the specified static fields + /// and no lazy loading task. + /// + /// # Arguments + /// + /// * `spec` - A vector of field descriptors (`stypes::FieldDesc`) that define the component's settings. + /// + /// # Returns + /// + /// * `Self` - A new `OptionsScheme` instance. + pub fn new(spec: Vec) -> Self { + Self { spec, lazy: None } + } + + /// Sets a lazy loading task for the current options scheme. + /// + /// This method associates a lazy loading task with the current scheme, allowing for + /// asynchronous loading of configuration fields that are not immediately available. + /// + /// # Arguments + /// + /// * `ident` - An identifier of the component for which the lazy task is created. + /// * `task` - An asynchronous task responsible for fetching lazy field data. + /// * `cancel` - A cancellation token that can interrupt the lazy loading process. + pub fn set_lazy( + &mut self, + ident: stypes::Ident, + task: LazyFieldsTask, + cancel: &CancellationToken, + ) { + self.lazy = Some(LazyLoadingTask::new(ident, task, cancel, self.lazy_uuids())); + } + + /// Extracts and removes all field descriptors from the options scheme. + /// + /// This method drains the vector of field descriptors, effectively emptying it, + /// and returns the collected fields as a new vector. + /// + /// # Returns + /// + /// * `Vec` - A vector containing the extracted field descriptors. + pub fn extract_spec(&mut self) -> Vec { + self.spec.drain(..).collect::>() + } + + /// Checks whether the options scheme contains any lazy fields. + /// + /// This method iterates over the field descriptors and checks for any field that + /// is marked as lazy, indicating that it requires asynchronous loading. + /// + /// # Returns + /// + /// * `true` if the scheme contains at least one lazy field. + /// * `false` if all fields are static. + pub fn has_lazy(&self) -> bool { + self.spec + .iter() + .any(|f| matches!(f, stypes::FieldDesc::Lazy(..))) + } + + /// Retrieves a list of UUIDs for all lazy fields in the options scheme. + /// + /// This method filters the field descriptors to extract only the IDs of lazy fields + /// and returns them as a vector of strings. + /// + /// # Returns + /// + /// * `Vec` - A vector containing the UUIDs of lazy fields. + fn lazy_uuids(&self) -> Vec { + self.spec + .iter() + .filter_map(|f| { + if let stypes::FieldDesc::Lazy(f) = f { + Some(f.id.to_owned()) + } else { + None + } + }) + .collect() + } +} diff --git a/application/apps/indexer/descriptor/src/tys/mod.rs b/application/apps/indexer/descriptor/src/tys/mod.rs new file mode 100644 index 000000000..0ace81e49 --- /dev/null +++ b/application/apps/indexer/descriptor/src/tys/mod.rs @@ -0,0 +1,261 @@ +mod parser; +mod source; + +use std::{future::Future, pin::Pin}; + +use crate::*; +pub use parser::*; +pub use source::*; +use stypes::SessionAction; +use tokio_util::sync::CancellationToken; + +/// A type alias for a result containing a vector of field descriptors or a native error. +/// +/// # Variants +/// +/// * `Ok(Vec)` - A vector containing field descriptors. +/// * `Err(stypes::NativeError)` - An error indicating a problem while retrieving the fields. +pub type FieldsResult = Result, stypes::NativeError>; + +#[derive(Debug)] +/// A type alias representing the result of obtaining a single static field description. +pub enum StaticFieldResult { + Success(stypes::StaticFieldDesc), + Failed(stypes::FieldLoadingError), +} + +impl StaticFieldResult { + pub fn is_ok(&self) -> bool { + matches!(self, StaticFieldResult::Success(..)) + } + pub fn ok(self) -> Option { + match self { + Self::Success(desc) => Some(desc), + Self::Failed(..) => None, + } + } + pub fn err(self) -> Option { + match self { + Self::Success(..) => None, + Self::Failed(err) => Some(err), + } + } +} + +/// A type alias for a result containing a vector of static field results or a native error. +/// +/// # Variants +/// +/// * `Ok(Vec)` - A vector containing static field results. +/// * `Err(stypes::NativeError)` - An error indicating a problem while retrieving static fields. +pub type StaticFieldsResult = Result, stypes::NativeError>; +/// A type alias for an asynchronous task that yields a static fields result. +/// +/// Represents a future that will eventually produce a `StaticFieldsResult`. +/// The task is wrapped in a `Pin>`, allowing for asynchronous +/// execution while being safely pinned in memory. +/// +/// # Output +/// +/// * `StaticFieldsResult` - A result containing static field data or a native error. +pub type LazyFieldsTask = Pin + Send>>; + +pub type Factory = fn( + &SessionAction, + &[stypes::Field], +) -> Result)>, stypes::NativeError>; + +/// Describes a component in terms of its identity, configuration schema, +/// validation logic, support for lazy-loading configuration, and its type within the system. +/// +/// The `CommonDescriptor` trait serves as an abstraction layer that decouples +/// the core system from concrete component implementations. Instead of referring +/// to component types directly (e.g., a specific parser or source), the application +/// interacts with components through their descriptors. +/// +/// This design enables a fully modular architecture where, for example, a session +/// can be created using a parser and source identified solely by their UUIDs. +/// The actual parser and source implementations remain hidden behind the descriptor, +/// making it possible to swap, reconfigure, or isolate components without touching +/// the application core. +pub trait CommonDescriptor: Sync + Send { + /// Check is component is campatible with given origin + /// + /// * `origin` - The source origin + /// + /// # Returns + /// + /// * `true` - if component can be used with origin + /// * `false` - if component can not be used with origin + fn is_compatible(&self, origin: &stypes::SessionAction) -> bool; + + /// Returns a general description of the component as a static, standalone entity. + /// + /// This method provides a neutral, context-independent identifier that can be used + /// to list the component as an available option for the user. Since the actual usage + /// context is unknown at this stage, the returned description should be as general + /// as possible. + /// + /// # Returns + /// A `stypes::Ident` representing the generic identity of the component. + fn ident(&self) -> stypes::Ident; + + /// Returns a context-specific description of the component, based on its usage origin and known fields. + /// + /// Unlike `ident`, this method considers how the component is being used, such as what + /// data source is involved and which fields are already known. For example, a `source::Raw` + /// component might normally describe itself as a "binary reader", but in the context of + /// a `SessionAction::File` and specific field values, it could instead describe itself + /// as a "filename" input. + /// + /// # Arguments + /// * `origin` - The `stypes::SessionAction` that indicates the usage context. + /// * `fields` - A slice of `stypes::Field` values representing known configuration fields. + /// + /// # Returns + /// An optional `stypes::Ident` with a contextualized description, or `None` if no + /// context-based identity is applicable. + fn bound_ident( + &self, + _origin: &stypes::SessionAction, + _fields: &[stypes::Field], + ) -> stypes::Ident { + self.ident() + } + + /// Retrieves the static field descriptors for the component. + /// + /// This method returns a list of field descriptors that are directly available without + /// requiring asynchronous loading. + /// + /// # Arguments + /// + /// * `origin` - The source origin related to the component. + /// + /// # Returns + /// + /// * `FieldsResult` - A result containing a vector of field descriptors or a native error. + #[allow(unused)] + fn fields_getter(&self, origin: &stypes::SessionAction) -> FieldsResult { + Ok(Vec::new()) + } + + /// Retrieves the lazy loading task for fields that require asynchronous fetching. + /// + /// This method returns a future that asynchronously loads the component's fields. + /// + /// # Arguments + /// + /// * `origin` - The source origin from which the data will be loaded. + /// * `cancel` - A cancellation token that can interrupt the loading process. + /// + /// # Returns + /// + /// * `LazyFieldsTask` - An asynchronous task that will eventually yield the field descriptors. + #[allow(unused)] + fn lazy_fields_getter( + &self, + origin: stypes::SessionAction, + cancel: CancellationToken, + ) -> LazyFieldsTask { + Box::pin(async { Ok(Vec::new()) }) + } + + /// Validates the provided settings for the component. + /// + /// This method checks whether the given fields are valid according to the component's + /// configuration rules. Since validation is optional, the default implementation + /// returns an empty map, indicating no validation issues. + /// + /// # Arguments + /// + /// * `origin` - The source origin related to the component. + /// * `fields` - A slice of fields to be validated. + /// + /// # Returns + /// + /// * `HashMap` - A map where keys are field names and values are + /// error messages, if any. An empty map means no validation errors. + /// + /// # Errors + /// Expected that returns error if some of fields aren't found. + #[allow(unused)] + fn validate( + &self, + origin: &stypes::SessionAction, + fields: &[stypes::Field], + ) -> Result, stypes::NativeError> { + Ok(HashMap::new()) + } + + /// Indicates whether the component (parser or source) provides default options. + /// + /// This method is used to determine if a component can be automatically selected + /// and instantiated in response to a user request, without requiring manual configuration. + /// For example, when a user opens a plain text file, there may be no need to configure + /// the source or parser manually. + /// + /// The method receives a [`SessionAction`] representing the context of the request. + /// If the component supports default configuration, it returns `Some(Vec)`, + /// where the fields can be used to create an instance of the component. + /// + /// If the component **must** be explicitly configured by the user (i.e., it cannot + /// be used blindly), the method returns `None`. + /// If the component has **no settings at all**, it returns `Some(Vec::new())` - + /// meaning it can be safely used without configuration. + /// + /// # Returns + /// + /// * `Some(Vec)` - Default fields to use for instantiating the component. + /// * `Some(Vec::new())` - The component has no settings but is still valid for auto-selection. + /// * `None` - The component requires explicit configuration and cannot be auto-selected. + /// + /// # Important + /// + /// Returning `None` will **exclude** the component from automatic selection logic. + fn get_default_options(&self, _origin: &stypes::SessionAction) -> Option> { + None + } +} + +/// Represents validation errors that occurred during client-side input processing. +/// +/// This structure is used to convey validation failures back to the client. +/// The client typically sends a list of fields as `(UUID as string, value)` pairs. +/// During validation, if any field contains invalid input, an error message is added +/// to the `errors` map, keyed by the stringified UUID of the field. These messages +/// are then displayed to the user in the UI. +#[derive(Debug, Default)] +pub struct ValidationErrors { + /// A map of field UUIDs (as strings) to corresponding validation error messages. + /// + /// Each key represents a specific field for which validation failed, + /// and the value is the error message intended for the user. + errors: HashMap, +} + +impl ValidationErrors { + /// Inserts a validation error message associated with a specific field. + /// + /// This method is typically called when a field fails a validation check, + /// such as range bounds, format constraints, or required value checks. + /// + /// # Arguments + /// + /// * `field` - The string identifier of the field (usually a UUID). + /// * `msg` - The error message to associate with the field. + pub fn insert_field_bound_err(&mut self, field: &str, msg: String) { + self.errors.insert(field.to_owned(), msg); + } + + /// Placeholder for reporting validation errors in a field that contains a list of values. + /// + /// This method is intended to be used when a specific value at a given index + /// in a collection (e.g., array, vector) is invalid. Currently, it is not implemented. + /// + /// # Arguments + /// + /// * `_field` - The string identifier of the field. + /// * `_idx` - The index of the invalid value in the list. + pub fn insert_values_bound_err(&mut self, _field: &str, _idx: usize) {} +} diff --git a/application/apps/indexer/descriptor/src/tys/parser.rs b/application/apps/indexer/descriptor/src/tys/parser.rs new file mode 100644 index 000000000..cf0d72e23 --- /dev/null +++ b/application/apps/indexer/descriptor/src/tys/parser.rs @@ -0,0 +1,64 @@ +use super::CommonDescriptor; + +/// This trait fully defines a parser entity (as it inherits the implementation of auxiliary traits) +/// and is used by the `Register` to store parser descriptors. +/// +/// ## Design Note +/// +/// In one of the earlier implementations, there was an attempt to separate the factory method (`create`) +/// into an independent entity. In other words, instead of storing +/// `HashMap<..., Box>` in the `Register`, the idea was to store +/// `HashMap<..., (FactoryFnPointer, Box)>` - i.e., separating the factory from the descriptor. +/// +/// This approach has clear drawbacks: +/// - First, it breaks the relationship between the `ParserDescriptor` and the factory. For example, +/// the compiler would not raise an error if a textual parser is registered with a descriptor +/// meant for a DLT parser. +/// - Second (and more critically), it prevents passing `self` into the factory method, which is essential +/// both for proper error validation and for cases where the descriptor itself contains +/// key information required for parser instantiation. This is especially relevant for plugins, +/// where the physical path to the plugin file (stored in the descriptor) must be accessible to the factory. +/// +/// Due to these factors, the decision was made to return to the original design, where the factory +/// is represented as a trait tightly coupled with the rest of the descriptor logic. +/// +/// ## Generic Type +/// +/// The trait is generic over type `T`, which always refers to the `parsers::Parsers` type. +/// The use of a generic parameter here serves only one purpose: to avoid cyclic dependencies +/// within the solution, while also preventing the need to excessively split the codebase +/// into small crates. +pub trait ParserFactory: ParserDescriptor { + fn create( + &self, + origin: &stypes::SessionAction, + options: &[stypes::Field], + ) -> Result)>, stypes::NativeError>; +} + +/// Defines parser-specific behavior and capabilities. +/// +/// This trait extends [`CommonDescriptor`] and provides an interface for +/// describing rendering behavior specific to a parser implementation. +/// +/// A parser may define which render should be used to display its content. +/// However, not all parsers are intended for rendering; some may serve +/// other purposes such as exporting or transforming data and thus return `None` +/// instead of a rendering strategy. +/// +/// Implementors of this trait should document the semantics of `get_render()` +/// in the context of their specific parser functionality. +pub trait ParserDescriptor: CommonDescriptor { + /// Returns the rendering strategy associated with this parser, if any. + /// + /// This method determines how (and whether) the parser's output should be + /// rendered in the user interface. If the parser is not intended to produce + /// visual output-e.g., when used for data export or structural analysis- + /// this function returns `None`. + /// + /// # Returns + /// + /// - `Some(OutputRender)` if the parser provides a specific rendering strategy. + /// - `None` if rendering is not applicable to this parser. + fn get_render(&self) -> Option; +} diff --git a/application/apps/indexer/descriptor/src/tys/source.rs b/application/apps/indexer/descriptor/src/tys/source.rs new file mode 100644 index 000000000..3d5f11c62 --- /dev/null +++ b/application/apps/indexer/descriptor/src/tys/source.rs @@ -0,0 +1,68 @@ +use super::CommonDescriptor; + +/// This trait fully defines a source entity (as it inherits the implementation of auxiliary traits) +/// and is used by the `Register` to store source descriptors. +/// +/// ## Design Note +/// +/// In one of the earlier implementations, there was an attempt to separate the factory method (`create`) +/// into an independent entity. In other words, instead of storing +/// `HashMap<..., Box>` in the `Register`, the idea was to store +/// `HashMap<..., (FactoryFnPointer, Box)>` - i.e., separating the factory from the descriptor. +/// +/// This approach has clear drawbacks: +/// - First, it breaks the relationship between the `SourceDescriptor` and the factory. For example, +/// the compiler would not raise an error if a tcp source is registered with a descriptor +/// meant for a serial source. +/// - Second (and more critically), it prevents passing `self` into the factory method, which is essential +/// both for proper error validation and for cases where the descriptor itself contains +/// key information required for source instantiation. This is especially relevant for plugins, +/// where the physical path to the plugin file (stored in the descriptor) must be accessible to the factory. +/// +/// Due to these factors, the decision was made to return to the original design, where the factory +/// is represented as a trait tightly coupled with the rest of the descriptor logic. +/// +/// ## Generic Type +/// +/// The trait is generic over type `T`, which always refers to the `sources::Sources` type. +/// The use of a generic parameter here serves only one purpose: to avoid cyclic dependencies +/// within the solution, while also preventing the need to excessively split the codebase +/// into small crates. +pub trait SourceFactory: SourceDescriptor { + fn create( + &self, + origin: &stypes::SessionAction, + options: &[stypes::Field], + ) -> Result)>, stypes::NativeError>; +} + +/// Defines source-specific capabilities and behaviors. +/// +/// This trait extends [`CommonDescriptor`] to describe features unique to data sources. +/// One such feature is support for SDE (Source Data Exchange), which allows the user +/// to send data back into the source instance. This is typically applicable in interactive +/// or bidirectional sources. +/// +/// For example, a `SerialPort` source may support SDE, enabling users to send commands +/// directly to the serial device. +pub trait SourceDescriptor: CommonDescriptor { + /// Indicates whether the source supports Source Data Exchange (SDE). + /// + /// When SDE is supported, the system allows the user to transmit data to the source, + /// such as sending raw binary streams or textual commands. + /// + /// This is commonly used in sources that enable two-way communication. + /// If the source does not support SDE, this method should return `false`. + /// + /// # Arguments + /// + /// * `_origin` - The session action that triggered the check (e.g., from the UI or script). + /// * `_fields` - A list of fields relevant to the current context or form submission. + /// + /// # Returns + /// + /// `true` if SDE is supported by the source, `false` otherwise. + fn is_sde_supported(&self, _origin: &stypes::SessionAction) -> bool { + false + } +} diff --git a/application/apps/indexer/parsers/Cargo.toml b/application/apps/indexer/parsers/Cargo.toml index f93b16798..5617e2721 100644 --- a/application/apps/indexer/parsers/Cargo.toml +++ b/application/apps/indexer/parsers/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -byteorder = "1.5" +# byteorder = "1.5" chrono = "0.4" chrono-tz = "0.10" dlt-core = { workspace = true, features = ["serialization", "fibex"] } @@ -13,11 +13,17 @@ log.workspace = true regex.workspace = true memchr = "2.7" serde = { workspace = true , features = ["derive"] } -thiserror.workspace = true rand.workspace = true someip-messages = { git = "https://github.com/esrlabs/someip" } someip-payload = { git = "https://github.com/esrlabs/someip-payload" } +uuid = { workspace = true , features = ["serde", "v4"] } +tokio-util.workspace = true +thiserror.workspace = true + +stypes = { path = "../stypes", features=["rustcore"] } +file-tools = { path = "../addons/file-tools" } someip-tools = { path = "../addons/someip-tools" } +descriptor = { path = "../descriptor" } [dev-dependencies] stringreader = "0.1" diff --git a/application/apps/indexer/parsers/src/api/mod.rs b/application/apps/indexer/parsers/src/api/mod.rs new file mode 100644 index 000000000..c93094bab --- /dev/null +++ b/application/apps/indexer/parsers/src/api/mod.rs @@ -0,0 +1,169 @@ +mod record; + +use crate::*; +use stypes::{NativeError, SessionAction}; +use thiserror::Error; + +pub use record::*; + +/// Defines the function type used to construct a parser instance for a session. +/// +/// This factory function is stored in the [`Register`] and invoked when a new session +/// is initialized. It produces a parser instance based on the provided session context +/// and configuration fields. +/// +/// The return value is a tuple: +/// - [`Parsers`] – An enum-wrapped instance of the constructed parser. +/// - `Option` – A human-readable label that describes the configured parser in context. +/// +/// The difference between this label and the one returned by [`CommonDescriptor`] is that +/// this one is **context-aware**. While `CommonDescriptor` may return a general name such as +/// `"DLT parser"`, this factory may return something more specific like +/// `"DLT with fibex /few/fibex.xml"` depending on user configuration or input. +/// +/// # Arguments +/// +/// * `&SessionAction` – The session action context (e.g., user-triggered session creation). +/// * `&[stypes::Field]` – A list of configuration fields provided by the client or system. +/// +/// # Returns +/// +/// * `Ok(Some((Parsers, Option)))` – A constructed parser instance with an optional +/// context-specific name for display. +/// * `Ok(None)` – Indicates that no parser should be created. +/// * `Err(NativeError)` – If parser creation fails due to misconfiguration or internal error. +/// +/// # Errors +/// +/// Returns an `Err(NativeError)` if the parser cannot be created due to invalid configuration, +/// missing required fields, or runtime failure during instantiation. +pub type ParserFactory = fn( + &SessionAction, + &[stypes::Field], +) -> Result)>, stypes::NativeError>; + +pub type ParseReturnIterator<'a> = + Result>)> + 'a>, ParserError>; + +#[derive(Error, Debug)] +pub enum ParserError { + #[error("Unrecoverable error, cannot continue: {0}")] + Unrecoverable(String), + #[error("Parse error: {0}")] + Parse(String), + #[error("Incomplete, not enough data for a message")] + Incomplete, + #[error("End of file reached")] + Eof, + #[error("{0}")] + Native(NativeError), +} + +impl From for ParserError { + fn from(err: NativeError) -> Self { + ParserError::Native(err) + } +} + +/// Parser trait that needs to be implemented for any parser we support +/// in chipmunk +pub trait Parser { + /// Takes a slice of bytes and try to apply a parser. If it can parse any item of them, + /// it will return iterator of items each with the consumed bytes count along with `Some(log_message)` + /// + /// if the slice does not have enough bytes to parse any item, an [`ParserError`] is returned. + /// + /// in case we could parse a message but the message was filtered out, `None` is returned on + /// that item. + /// + /// # Note: + /// + /// If the parsers encounter any error while it already has parsed any items, then it must + /// return those items without the error, then on the next call it can return the errors in + /// case it was provided with the same slice of bytes. + fn parse<'a>(&'a mut self, input: &'a [u8], timestamp: Option) -> ParseReturnIterator<'a>; +} + +/// A trait for parsers that extract one item at a time from a byte slice. +/// +/// Any type implementing this trait will automatically implement the [`Parser`] trait +/// due to a blanket implementation. This means that such types will support extracting +/// all available items from an input slice by repeatedly calling [`SingleParser::parse_item()`] +/// until no more items can be parsed. +/// +/// # Behavior +/// +/// - The blanket implementation of [`Parser`] will repeatedly invoke `parse_item()`, +/// extracting as many items as possible. +/// - If `parse_item()` fails on the first call, an error is returned immediately. +/// - If `parse_item()` succeeds, parsing continues until: +/// - The remaining input is too short to parse another item. +/// - `parse_item()` returns an error, which is ignored after the first successful parse. +pub trait SingleParser { + /// The minimum number of bytes required to parse an item. + /// + /// # Notes: + /// - This value is used to prevent unnecessary parsing attempts when the remaining input + /// is too short to contain a valid message. + /// - The default value (`1`) indicates that the parser has no minimum length requirement. + const MIN_MSG_LEN: usize = 1; + + /// Parses a single item from the given byte slice. + /// + /// in case we could parse a message but the message was filtered out, `None` is returned on + /// that item. + fn parse_item<'a>( + &mut self, + input: &'a [u8], + timestamp: Option, + ) -> Result<(usize, Option>), ParserError>; +} + +/// This blanket implementation repeatedly applies [`SingleParser::parse_item()`] function, +/// extracting as many items as possible from the provided input until no more can be parsed. +impl

Parser for P +where + P: SingleParser, +{ + fn parse<'a>(&'a mut self, input: &'a [u8], timestamp: Option) -> ParseReturnIterator<'a> { + let mut slice = input; + + // return early if function errors on first parse call. + let first_res = self.parse_item(slice, timestamp)?; + + // Otherwise keep parsing and stop on first error, returning the parsed items at the end. + let iter = std::iter::successors(Some(first_res), move |(consumed, _res)| { + slice = &slice[*consumed..]; + + if slice.len() < P::MIN_MSG_LEN { + return None; + } + + self.parse_item(slice, timestamp).ok() + }); + + Ok(Box::new(iter)) + } +} + +//TODO AAZ: This could be removed? +pub trait Collector { + fn register_message(&mut self, offset: usize, msg: &T); + fn attachment_indexes(&self) -> Vec; +} + +pub trait LineFormat { + fn format_line(&self) -> String; +} + +pub enum ByteRepresentation { + Owned(Vec), + Range((usize, usize)), +} + +#[derive(Debug)] +pub enum MessageStreamItem { + Parsed(ParseOperationResult), + Skipped, + Done, +} diff --git a/application/apps/indexer/parsers/src/api/record.rs b/application/apps/indexer/parsers/src/api/record.rs new file mode 100644 index 000000000..9b624686a --- /dev/null +++ b/application/apps/indexer/parsers/src/api/record.rs @@ -0,0 +1,171 @@ +use serde::Serialize; +use std::borrow::Cow; + +#[cfg(test)] +use std::fmt; + +pub const COLUMN_SENTINAL: char = '\u{0004}'; + +/// Some log records (e.g., in binary formats like DLT) may include attached files. +/// This structure describes such attachments. +#[derive(Debug, Clone, Serialize)] +pub struct Attachment { + /// File name. + pub name: String, + /// File size in bytes. + pub size: usize, + /// File creation date, if available. + pub created_date: Option, + /// File modification date, if available. + pub modified_date: Option, + /// Indexes of the log messages (0-based) from which the file is composed. + pub messages: Vec, + /// File content. + pub data: Vec, +} + +impl Attachment { + /// Appends additional data to the attachment. + /// + /// Since the content of a file may be split across multiple log messages, + /// this method allows merging the new chunk into the existing buffer. + /// + /// # Arguments + /// * `new_data` – A byte slice containing the next part of the file. + pub fn add_data(&mut self, new_data: &[u8]) { + self.data.extend_from_slice(new_data); + } +} + +/// Represents the result of a single parsing step. +/// +/// This structure is used to report the outcome of log parsing from `MessageProducer` +/// to its controlling logic. +#[derive(Debug)] +pub struct ParseOperationResult { + /// Number of bytes successfully consumed from the input buffer. + pub consumed: usize, + /// Number of messages that were parsed and forwarded. + pub count: usize, +} + +impl ParseOperationResult { + pub fn new(consumed: usize, count: usize) -> Self { + Self { consumed, count } + } + pub fn parsed_any_msg(&self) -> bool { + self.count > 0 + } +} + +/// Defines the shape of data that a parser can emit to a `MessageProducer`. +/// +/// # Design Philosophy +/// +/// Only the parser has full knowledge of the semantics and structure of the data +/// it processes. Therefore, the parser alone should decide how to best represent +/// that data. For example, a DLT parser naturally produces columnar data, while +/// another parser might emit raw binary content. +/// +/// Previous approaches attempted to force all parser outputs to conform to a trait +/// like `LogMessage`, requiring implementations of `to_string()` or `write()` methods. +/// This contradicts the role of the parser, which is **not** responsible for data +/// conversion or formatting. +/// +/// Instead, the parser emits data in its native structure, and it is the responsibility +/// of higher-level components (like `MessageProducer`) to format, serialize, or +/// store it as needed. For example, in the case of DLT: +/// +/// - The parser should emit structured columns. +/// - The `MessageProducer` should determine how to format those columns into lines +/// and write them to a session file. +/// +/// If different output is required (e.g., exporting DLT data as raw bytes), a different +/// parser implementation (such as `DltRaw`) should be used. +#[derive(Debug)] +pub enum LogRecordOutput<'a> { + /// A raw binary message. + Raw(&'a [u8]), + + /// Log message line + Message(Cow<'a, str>), + + /// Structured columnar data. Typically used when the parser can extract + /// meaningful fields and present them as collection of texts. + Columns(Vec>), + + /// An attachment object, such as a binary blob or associated metadata. + /// These are handled separately from textual data and require special treatment + /// during output (e.g., saved as files, linked from logs, etc.). + Attachment(Attachment), + + /// A compound message containing multiple outputs found during a single parsing iteration. + /// This is useful when the parser extracts several independent pieces of data at once. + Multiple(Vec>), +} + +#[cfg(test)] +impl<'a> fmt::Display for LogRecordOutput<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LogRecordOutput::Raw(inner) => { + write!( + f, + "{}", + &inner + .iter() + .map(|b| format!("{:02X}", b)) + .collect::() + ) + } + LogRecordOutput::Message(msg) => { + write!(f, "{msg}") + } + LogRecordOutput::Columns(inner) => { + write!(f, "{}", inner.join(&COLUMN_SENTINAL.to_string())) + } + LogRecordOutput::Multiple(inner) => { + for rec in inner { + write!(f, "{}", rec)?; + } + Ok(()) + } + LogRecordOutput::Attachment(inner) => { + write!(f, "{:?}", inner) + } + } + } +} + +/// Defines an interface for buffering log records before they are persisted. +/// +/// An instance of a type implementing `LogRecordsBuffer` is passed to a `MessageProducer`. +/// For each new log record, the `MessageProducer` calls the `append` method. The `flush` +/// method is used to write the buffered records to their final destination. +/// +/// This design allows for custom strategies regarding caching, batching, and flushing. +/// When processing is complete, `flush` should be called to ensure all buffered data +/// is persisted. +/// +/// # Examples +/// An implementation might buffer records in memory and flush them in bulk +/// to a file, database, or network sink. +pub trait LogRecordsBuffer { + /// Appends the provided `record` to the writer intermediate memory. + /// + /// # Arguments + /// * `record` – A reference to a [`LogRecordOutput`] containing the data to write. + fn append(&mut self, record: LogRecordOutput<'_>); + + /// Flushes the data from the internal buffer to the underline target + async fn flush(&mut self) -> Result<(), stypes::NativeError>; + + /// Returns the unique identifier of the associated data source. + /// + /// This ID can be used to correlate logs with their origin, + /// especially in multi-source scenarios. + /// + /// # Returns + /// A `u16` representing the unique source ID. + fn get_source_id(&self) -> u16; +} diff --git a/application/apps/indexer/parsers/src/dlt/attachment.rs b/application/apps/indexer/parsers/src/dlt/attachment.rs index d9400f42a..70651cde4 100644 --- a/application/apps/indexer/parsers/src/dlt/attachment.rs +++ b/application/apps/indexer/parsers/src/dlt/attachment.rs @@ -1,7 +1,7 @@ use dlt_core::dlt::{Argument, LogLevel, Message, MessageType, PayloadContent, Value}; use std::{collections::HashMap, path::PathBuf}; -use crate::Attachment; +use crate::*; const FT_START_TAG: &str = "FLST"; const FT_DATA_TAG: &str = "FLDA"; diff --git a/application/apps/indexer/parsers/src/dlt/descriptor.rs b/application/apps/indexer/parsers/src/dlt/descriptor.rs new file mode 100644 index 000000000..d28a75d06 --- /dev/null +++ b/application/apps/indexer/parsers/src/dlt/descriptor.rs @@ -0,0 +1,374 @@ +use crate::{Parsers, dlt::*}; +use ::descriptor::{ + CommonDescriptor, FieldsResult, LazyFieldsTask, ParserDescriptor, ParserFactory, + StaticFieldResult, +}; +use dlt_core::{ + read::DltMessageReader, + statistics::{ + collect_statistics, + common::{StatisticInfo, StatisticInfoCollector}, + }, +}; +use std::fmt; +use std::fs::File; +use std::{collections::HashMap, path::PathBuf}; +use stypes::{ + ComponentOptions, ExtractByKey, Field, FieldDesc, NativeError, NativeErrorKind, SessionAction, + Severity, StaticFieldDesc, Value, ValueInput, missed_field_err as missed, +}; +use tokio_util::sync::CancellationToken; + +const DLT_PARSER_UUID: uuid::Uuid = uuid::Uuid::from_bytes([ + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +]); + +const FIELD_LOG_LEVEL: &str = "DLT_PARSER_FIELD_LOG_LEVEL"; +const FIELD_FIBEX_FILES: &str = "DLT_PARSER_FIELD_FIBEX_FILES"; +const FIELD_STATISTICS: &str = "DLT_PARSER_FIELD_STATISTICS"; +const FIELD_TZ: &str = "DLT_PARSER_FIELD_TIMEZONE"; + +#[derive(Default)] +pub struct Descriptor {} + +enum StatFields { + AppIds, + ContextIds, + EcuIds, +} + +impl fmt::Display for StatFields { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + Self::AppIds => "app_ids", + Self::ContextIds => "context_ids", + Self::EcuIds => "ecu_ids", + } + ) + } +} + +impl ParserFactory for Descriptor { + fn create( + &self, + origin: &stypes::SessionAction, + options: &[stypes::Field], + ) -> Result)>, stypes::NativeError> { + let errors = self.validate(origin, options)?; + if !errors.is_empty() { + return Err(NativeError { + kind: NativeErrorKind::Configuration, + severity: Severity::ERROR, + message: Some( + errors + .values() + .map(String::as_str) + .collect::>() + .join("; "), + ), + }); + } + let fibex_file_paths: &Vec = options + .extract_by_key(FIELD_FIBEX_FILES) + .ok_or(missed(FIELD_FIBEX_FILES))? + .value; + let dlt_metadata = dlt_core::fibex::gather_fibex_data(dlt_core::fibex::FibexConfig { + fibex_file_paths: fibex_file_paths + .iter() + .map(|p| p.to_string_lossy().to_string()) + .collect(), + }); + let someip_metadata = FibexSomeipMetadata::from_fibex_files(fibex_file_paths); + let min_log_level = u8_to_log_level( + options + .extract_by_key(FIELD_LOG_LEVEL) + .ok_or(missed(FIELD_LOG_LEVEL))? + .value, + ) + .ok_or(missed(FIELD_LOG_LEVEL))?; + let stats: &HashMap> = options + .extract_by_key(FIELD_STATISTICS) + .ok_or(missed(FIELD_STATISTICS))? + .value; + let filter_config: ProcessedDltFilterConfig = ProcessedDltFilterConfig { + min_log_level: Some(min_log_level), + app_ids: stats + .get(&StatFields::AppIds.to_string()) + .map(|fields| fields.iter().cloned().collect()), + ecu_ids: stats + .get(&StatFields::EcuIds.to_string()) + .map(|fields| fields.iter().cloned().collect()), + context_ids: stats + .get(&StatFields::ContextIds.to_string()) + .map(|fields| fields.iter().cloned().collect()), + app_id_count: 0, + context_id_count: 0, + }; + Ok(Some(( + crate::Parsers::Dlt(DltParser::new( + Some(filter_config), + dlt_metadata, + None, + someip_metadata, + // If it's source - no storage header expected + !matches!(origin, SessionAction::Source), + )), + Some("DLT".to_owned()), + ))) + } +} + +impl CommonDescriptor for Descriptor { + fn is_compatible(&self, origin: &SessionAction) -> bool { + let files = match origin { + SessionAction::File(filepath) => { + vec![filepath] + } + SessionAction::Files(files) => files.iter().collect(), + SessionAction::Source => return true, + SessionAction::ExportRaw(..) => return false, + }; + files.iter().any(|fp| { + fp.extension() + .map(|ext| ext.eq_ignore_ascii_case("dlt")) + .unwrap_or_default() + }) + } + fn fields_getter(&self, origin: &SessionAction) -> FieldsResult { + let mut options = vec![ + FieldDesc::Static(StaticFieldDesc { + id: FIELD_LOG_LEVEL.to_owned(), + name: "Log Level".to_owned(), + desc: "Log Level - defines the minimum severity level of log messages to be displayed. Messages with lower severity (e.g., Debug or Verbose) will be filtered out. For example, if \"Error\" is selected, only messages with level \"Error\" and above (e.g., Fatal) will be shown, while lower levels such as \"Warning\" or \"Debug\" will be ignored.".to_owned(), + required: true, + interface: ValueInput::NamedNumbers( + vec![ + ("Fatal".to_owned(), 1), + ("Error".to_owned(), 2), + ("Warn".to_owned(), 3), + ("Info".to_owned(), 4), + ("Debug".to_owned(), 5), + ("Verbose".to_owned(), 6), + ], + 6, + ), + binding: None, + }), + FieldDesc::Static(StaticFieldDesc { + id: FIELD_FIBEX_FILES.to_owned(), + name: "Fibex Files".to_owned(), + desc: "Fibex Files - allows the user to attach one or more XML files containing the schema definitions for decoding the DLT payload. These files are used to interpret the payload contents according to the provided descriptions. If decoding fails or no matching schema is found, the payload will be displayed as-is, in raw hexadecimal form.".to_owned(), + required: true, + interface: ValueInput::Files(vec!["xml".to_owned(), "*".to_owned()]), + binding: None, + }), + FieldDesc::Static(StaticFieldDesc { + id: FIELD_TZ.to_owned(), + name: "Timezone".to_owned(), + desc: "Timezone - a display-only setting used for visualizing timestamps. If a timezone is specified, all DLT timestamps will be adjusted accordingly to improve readability. This setting does not modify the underlying data and is used purely for presentation purposes.".to_owned(), + required: true, + interface: ValueInput::Timezone, + binding: None, + }), + ]; + if matches!(origin, SessionAction::File(..) | SessionAction::Files(..)) { + options.push(stypes::FieldDesc::Lazy(stypes::LazyFieldDesc { + id: FIELD_STATISTICS.to_owned(), + name: String::from("Statistics"), + desc: String::from("Collected Statistis Data"), + binding: None, + })); + } + Ok(options) + } + fn ident(&self) -> stypes::Ident { + stypes::Ident { + name: String::from("DLT"), + desc: String::from( + "DLT Parser is a binary parser for decoding AUTOSAR DLT (Diagnostic Log and Trace) messages. It processes raw binary input and extracts structured log information according to the DLT protocol specification. The parser can be applied both to files (typically containing a StorageHeader) and to live streams over TCP or UDP. It expects binary data as input and does not perform any framing or transport-level parsing.", + ), + io: stypes::IODataType::Multiple(vec![ + stypes::IODataType::NetworkFramePayload, + stypes::IODataType::Raw, + ]), + uuid: DLT_PARSER_UUID, + } + } + fn lazy_fields_getter( + &self, + origin: SessionAction, + cancel: CancellationToken, + ) -> LazyFieldsTask { + Box::pin(async move { + let file_paths = match origin { + SessionAction::File(fp) => { + vec![fp] + } + SessionAction::Files(fps) => fps, + _ => { + return Ok(vec![StaticFieldResult::Success(stypes::StaticFieldDesc { + id: FIELD_STATISTICS.to_owned(), + name: String::from("Example"), + desc: String::from("Example"), + required: true, + interface: ValueInput::KeyString(HashMap::new()), + binding: None, + })]); + } + }; + let mut stat = StatisticInfo::new(); + let mut error: Option = None; + file_paths.iter().for_each(|file_path| { + if error.is_some() || cancel.is_cancelled() { + return; + } + match File::open(file_path) { + Ok(file) => { + let mut reader = DltMessageReader::new(file, true); + let mut collector = StatisticInfoCollector::default(); + match collect_statistics(&mut reader, &mut collector) { + Ok(()) => { + stat.merge(collector.collect()); + } + Err(err) => { + error = Some(err.to_string()); + } + } + } + Err(err) => { + error = Some(err.to_string()); + } + } + }); + if cancel.is_cancelled() { + return Err(NativeError { + kind: NativeErrorKind::Interrupted, + severity: Severity::WARNING, + message: Some("Operation has been cancelled".to_owned()), + }); + } + if let Some(err) = error { + return Err(NativeError { + kind: NativeErrorKind::Io, + severity: Severity::ERROR, + message: Some(err), + }); + } + static NON_LOG: &str = "non_log"; + static LOG_FATAL: &str = "log_fatal"; + static LOG_ERROR: &str = "log_error"; + static LOG_WARNING: &str = "log_warning"; + static LOG_INFO: &str = "log_info"; + static LOG_DEBUG: &str = "log_debug"; + static LOG_VERBOSE: &str = "log_verbose"; + static LOG_INVALID: &str = "log_invalid"; + let mut converted: HashMap>> = + HashMap::new(); + [ + StatFields::AppIds, + StatFields::ContextIds, + StatFields::EcuIds, + ] + .into_iter() + .for_each(|key| { + let inner = match key { + StatFields::AppIds => &stat.app_ids, + StatFields::ContextIds => &stat.context_ids, + StatFields::EcuIds => &stat.ecu_ids, + }; + let mut entity: HashMap> = HashMap::new(); + inner.iter().for_each(|(id, levels)| { + let mut map: HashMap = HashMap::new(); + map.insert(NON_LOG.to_owned(), levels.non_log); + map.insert(LOG_FATAL.to_owned(), levels.log_fatal); + map.insert(LOG_ERROR.to_owned(), levels.log_error); + map.insert(LOG_WARNING.to_owned(), levels.log_warning); + map.insert(LOG_INFO.to_owned(), levels.log_info); + map.insert(LOG_DEBUG.to_owned(), levels.log_debug); + map.insert(LOG_VERBOSE.to_owned(), levels.log_verbose); + map.insert(LOG_INVALID.to_owned(), levels.log_invalid); + entity.insert(id.to_owned(), map); + }); + converted.insert(key.to_string(), entity); + }); + Ok(vec![StaticFieldResult::Success(stypes::StaticFieldDesc { + id: FIELD_STATISTICS.to_owned(), + name: String::from("Statistics"), + desc: String::from("Dlt File(s) Statistics data"), + required: true, + interface: ValueInput::NestedNumbersMap( + converted, + vec![ + (String::from("non_log"), String::from("NON LOG")), + (String::from("log_fatal"), String::from("FATAL")), + (String::from("log_error"), String::from("ERROR")), + (String::from("log_warning"), String::from("WARNING")), + (String::from("log_info"), String::from("INFO")), + (String::from("log_debug"), String::from("DEBUG")), + (String::from("log_verbose"), String::from("VERBOSE")), + (String::from("log_invalid"), String::from("INVALID")), + (String::from("context_ids"), String::from("CONTEXT LIST")), + ] + .into_iter() + .collect(), + ), + binding: None, + })]) + }) + } +} + +impl ParserDescriptor for Descriptor { + fn get_render(&self) -> Option { + Some(stypes::OutputRender::Columns(vec![ + ("Datetime".to_owned(), 150), + ("ECUID".to_owned(), 80), + ("VERS".to_owned(), 80), + ("SID".to_owned(), 80), + ("MCNT".to_owned(), 80), + ("TMS".to_owned(), 80), + ("EID".to_owned(), 80), + ("APID".to_owned(), 80), + ("CTID".to_owned(), 80), + ("MSTP".to_owned(), 80), + ("PAYLOAD".to_owned(), 0), + ])) + } +} +fn u8_to_log_level(level: u8) -> Option { + match level { + 1 => Some(dlt::LogLevel::Fatal), + 2 => Some(dlt::LogLevel::Error), + 3 => Some(dlt::LogLevel::Warn), + 4 => Some(dlt::LogLevel::Info), + 5 => Some(dlt::LogLevel::Debug), + 6 => Some(dlt::LogLevel::Verbose), + _ => None, + } +} + +pub fn get_default_options( + fibex: Option>, + filters: Option>>, +) -> ComponentOptions { + ComponentOptions { + uuid: DLT_PARSER_UUID, + fields: vec![ + Field { + id: FIELD_FIBEX_FILES.to_owned(), + value: Value::Files(fibex.unwrap_or_default()), + }, + Field { + id: FIELD_LOG_LEVEL.to_owned(), + value: Value::Number(6), + }, + Field { + id: FIELD_STATISTICS.to_owned(), + value: Value::KeyStrings(filters.unwrap_or_default()), + }, + ], + } +} diff --git a/application/apps/indexer/parsers/src/dlt/fmt.rs b/application/apps/indexer/parsers/src/dlt/fmt.rs index 9d4a6b793..837afc8ad 100644 --- a/application/apps/indexer/parsers/src/dlt/fmt.rs +++ b/application/apps/indexer/parsers/src/dlt/fmt.rs @@ -213,7 +213,7 @@ pub struct FormattableMessage<'a> { pub options: Option<&'a FormatOptions>, } -impl Serialize for FormattableMessage<'_> { +impl<'a> Serialize for FormattableMessage<'a> { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -310,7 +310,7 @@ impl Serialize for FormattableMessage<'_> { } } -impl From for FormattableMessage<'_> { +impl<'a> From for FormattableMessage<'a> { fn from(message: Message) -> Self { FormattableMessage { message, @@ -344,7 +344,7 @@ impl<'a> PrintableMessage<'a> { } } -impl FormattableMessage<'_> { +impl<'a> FormattableMessage<'a> { pub fn printable_parts<'b>( &'b self, ext_h_app_id: &'b str, @@ -500,7 +500,7 @@ impl FormattableMessage<'_> { } fn info_from_metadata<'b>(&'b self, id: u32, data: &[u8]) -> Option> { - let fibex = self.fibex_dlt_metadata?; + let fibex = self.fibex_dlt_metadata.as_ref()?; let md = extract_metadata(fibex, id, self.message.extended_header.as_ref())?; let msg_type: Option = message_type(&self.message, md.message_info.as_deref()); let app_id = md.application_id.as_deref().or_else(|| { @@ -549,7 +549,7 @@ impl FormattableMessage<'_> { } } -impl fmt::Display for FormattableMessage<'_> { +impl<'a> fmt::Display for FormattableMessage<'a> { /// will format dlt Message with those fields: /// ********* storage-header ******** /// date-time @@ -568,7 +568,7 @@ impl fmt::Display for FormattableMessage<'_> { /// payload fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { if let Some(h) = &self.message.storage_header { - let tz = self.options.map(|o| o.tz); + let tz = self.options.as_ref().map(|o| o.tz); match tz { Some(Some(tz)) => { write_tz_string(f, &h.timestamp, &tz)?; diff --git a/application/apps/indexer/parsers/src/dlt/mod.rs b/application/apps/indexer/parsers/src/dlt/mod.rs index 68cdf16a7..c88938340 100644 --- a/application/apps/indexer/parsers/src/dlt/mod.rs +++ b/application/apps/indexer/parsers/src/dlt/mod.rs @@ -1,14 +1,16 @@ pub mod attachment; +pub mod descriptor; pub mod fmt; +pub mod raw; +use self::{attachment::FtScanner, fmt::FormatOptions}; use crate::{ - Error, LogMessage, ParseYield, SingleParser, dlt::fmt::FormattableMessage, + Attachment, LogRecordOutput, ParserError, SingleParser, dlt::fmt::FormattableMessage, someip::FibexMetadata as FibexSomeipMetadata, }; -use byteorder::{BigEndian, WriteBytesExt}; use dlt_core::{ dlt, - parse::{DltParseError, dlt_consume_msg, dlt_message}, + parse::{DltParseError, dlt_message}, }; pub use dlt_core::{ dlt::LogLevel, @@ -16,100 +18,39 @@ pub use dlt_core::{ filtering::{DltFilterConfig, ProcessedDltFilterConfig}, }; use serde::Serialize; -use std::{io::Write, ops::Range}; - -use self::{attachment::FtScanner, fmt::FormatOptions}; +use stypes::{NativeError, NativeErrorKind, Severity}; /// The most likely minimal bytes count needed to parse a DLT message. const MIN_MSG_LEN: usize = 20; -impl LogMessage for FormattableMessage<'_> { - fn to_writer(&self, writer: &mut W) -> Result { - let bytes = self.message.as_bytes(); - let len = bytes.len(); - writer.write_all(&bytes)?; - Ok(len) - } -} - #[derive(Debug, Serialize)] pub struct RawMessage { pub content: Vec, } -#[derive(Debug, Serialize)] -pub struct RangeMessage { - pub range: Range, -} - -impl std::fmt::Display for RangeMessage { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "({:?})", self.range) - } -} impl std::fmt::Display for RawMessage { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "[{:02X?}]", &self.content) } } -impl LogMessage for RangeMessage { - /// A RangeMessage only has range information and cannot serialize to bytes - fn to_writer(&self, writer: &mut W) -> Result { - writer.write_u64::(self.range.start as u64)?; - writer.write_u64::(self.range.end as u64)?; - Ok(8 + 8) - } -} - -impl LogMessage for RawMessage { - fn to_writer(&self, writer: &mut W) -> Result { - let len = self.content.len(); - writer.write_all(&self.content)?; - Ok(len) - } -} - #[derive(Default)] -pub struct DltParser<'m> { +pub struct DltParser { pub filter_config: Option, - pub fibex_dlt_metadata: Option<&'m FibexDltMetadata>, - pub fmt_options: Option<&'m FormatOptions>, + pub fibex_dlt_metadata: Option, + pub fmt_options: Option, pub with_storage_header: bool, ft_scanner: FtScanner, - fibex_someip_metadata: Option<&'m FibexSomeipMetadata>, - offset: usize, -} - -#[derive(Default)] -pub struct DltRangeParser { + fibex_someip_metadata: Option, offset: usize, } -pub struct DltRawParser { - pub with_storage_header: bool, -} - -impl DltRawParser { - pub fn new(with_storage_header: bool) -> Self { - Self { - with_storage_header, - } - } -} - -impl DltRangeParser { - pub fn new() -> Self { - Self { offset: 0 } - } -} - -impl<'m> DltParser<'m> { +impl DltParser { pub fn new( filter_config: Option, - fibex_dlt_metadata: Option<&'m FibexDltMetadata>, - fmt_options: Option<&'m FormatOptions>, - fibex_someip_metadata: Option<&'m FibexSomeipMetadata>, + fibex_dlt_metadata: Option, + fmt_options: Option, + fibex_someip_metadata: Option, with_storage_header: bool, ) -> Self { Self { @@ -122,35 +63,22 @@ impl<'m> DltParser<'m> { offset: 0, } } -} - -impl From for Error { - fn from(value: DltParseError) -> Self { - match value { - DltParseError::Unrecoverable(e) | DltParseError::ParsingHickup(e) => { - Error::Parse(e.to_string()) - } - DltParseError::IncompleteParse { needed: _ } => Error::Incomplete, - } - } -} - -impl<'m> SingleParser> for DltParser<'m> { - const MIN_MSG_LEN: usize = MIN_MSG_LEN; fn parse_item( &mut self, input: &[u8], timestamp: Option, - ) -> Result<(usize, Option>>), Error> { - match dlt_message(input, self.filter_config.as_ref(), self.with_storage_header)? { + ) -> Result<(usize, Option<(FormattableMessage<'_>, Option)>), ParserError> { + match dlt_message(input, self.filter_config.as_ref(), self.with_storage_header) + .map_err(map_dlt_err)? + { (rest, dlt_core::parse::ParsedMessage::FilteredOut(_n)) => { let consumed = input.len() - rest.len(); self.offset += consumed; Ok((consumed, None)) } (_, dlt_core::parse::ParsedMessage::Invalid) => { - Err(Error::Parse("Invalid parse".to_owned())) + Err(ParserError::Parse("Invalid parse".to_owned())) } (rest, dlt_core::parse::ParsedMessage::Item(i)) => { let attachment = self.ft_scanner.process(&i); @@ -162,67 +90,56 @@ impl<'m> SingleParser> for DltParser<'m> { let msg = FormattableMessage { message: msg_with_storage_header, - fibex_dlt_metadata: self.fibex_dlt_metadata, - options: self.fmt_options, - fibex_someip_metadata: self.fibex_someip_metadata, + fibex_dlt_metadata: self.fibex_dlt_metadata.as_ref(), + options: self.fmt_options.as_ref(), + fibex_someip_metadata: self.fibex_someip_metadata.as_ref(), }; let consumed = input.len() - rest.len(); self.offset += consumed; - let item = ( - consumed, - if let Some(attachment) = attachment { - Some(ParseYield::MessageAndAttachment((msg, attachment))) - } else { - Some(ParseYield::Message(msg)) - }, - ); - - Ok(item) + Ok((consumed, Some((msg, attachment)))) } } } } -impl SingleParser for DltRangeParser { - const MIN_MSG_LEN: usize = MIN_MSG_LEN; - - fn parse_item( - &mut self, - input: &[u8], - _timestamp: Option, - ) -> Result<(usize, Option>), Error> { - let (rest, consumed) = dlt_consume_msg(input)?; - let msg = consumed.map(|c| { - self.offset += c as usize; - RangeMessage { - range: Range { - start: self.offset, - end: self.offset + c as usize, - }, - } - }); - let total_consumed = input.len() - rest.len(); - let item = (total_consumed, msg.map(|m| m.into())); - - Ok(item) +fn map_dlt_err(err: DltParseError) -> ParserError { + match err { + DltParseError::Unrecoverable(e) | DltParseError::ParsingHickup(e) => { + ParserError::Parse(e.to_string()) + } + DltParseError::IncompleteParse { needed: _ } => ParserError::Incomplete, } } -impl SingleParser for DltRawParser { +impl SingleParser for DltParser { const MIN_MSG_LEN: usize = MIN_MSG_LEN; - fn parse_item( + fn parse_item<'a>( &mut self, - input: &[u8], - _timestamp: Option, - ) -> Result<(usize, Option>), Error> { - let (rest, consumed) = dlt_consume_msg(input)?; - let msg = consumed.map(|c| RawMessage { - content: Vec::from(&input[0..c as usize]), - }); - let total_consumed = input.len() - rest.len(); - let item = (total_consumed, msg.map(|m| m.into())); + input: &'a [u8], + timestamp: Option, + ) -> Result<(usize, Option>), ParserError> { + let (consumed, data) = self.parse_item(input, timestamp)?; + Ok(( + consumed, + data.map(|(msg, attachment)| { + if let Some(attachment) = attachment { + LogRecordOutput::Multiple(vec![ + LogRecordOutput::Message(msg.to_string().into()), + LogRecordOutput::Attachment(attachment), + ]) + } else { + LogRecordOutput::Message(msg.to_string().into()) + } + }), + )) + } +} - Ok(item) +fn to_native_cfg_err(msg: S) -> NativeError { + NativeError { + severity: Severity::ERROR, + kind: NativeErrorKind::Configuration, + message: Some(msg.to_string()), } } diff --git a/application/apps/indexer/parsers/src/dlt/raw/descriptor.rs b/application/apps/indexer/parsers/src/dlt/raw/descriptor.rs new file mode 100644 index 000000000..9bf2f022b --- /dev/null +++ b/application/apps/indexer/parsers/src/dlt/raw/descriptor.rs @@ -0,0 +1,70 @@ +use super::*; +use crate::*; +use ::descriptor::{ + CommonDescriptor, FieldsResult, LazyFieldsTask, ParserDescriptor, ParserFactory, +}; +use stypes::SessionAction; +use tokio_util::sync::CancellationToken; + +const DLT_PARSER_UUID: uuid::Uuid = uuid::Uuid::from_bytes([ + 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +]); + +#[derive(Default)] +pub struct Descriptor {} + +impl ParserFactory for Descriptor { + fn create( + &self, + origin: &stypes::SessionAction, + _options: &[stypes::Field], + ) -> Result)>, stypes::NativeError> { + Ok(Some(( + Parsers::DltRaw(DltRawParser::new(!matches!(origin, SessionAction::Source))), + Some("DLT".to_string()), + ))) + } +} + +impl CommonDescriptor for Descriptor { + fn is_compatible(&self, origin: &SessionAction) -> bool { + let files = match origin { + SessionAction::File(..) | SessionAction::Files(..) | SessionAction::Source => { + return false; + } + SessionAction::ExportRaw(files, ..) => files, + }; + files.iter().any(|fp| { + fp.extension() + .map(|ext| ext.eq_ignore_ascii_case("dlt")) + .unwrap_or_default() + }) + } + fn fields_getter(&self, _origin: &SessionAction) -> FieldsResult { + Ok(Vec::new()) + } + fn ident(&self) -> stypes::Ident { + stypes::Ident { + name: String::from("DLT Export Parser"), + desc: String::from("DLT Export Parser"), + io: stypes::IODataType::Multiple(vec![ + stypes::IODataType::NetworkFramePayload, + stypes::IODataType::Raw, + ]), + uuid: DLT_PARSER_UUID, + } + } + fn lazy_fields_getter( + &self, + _origin: SessionAction, + _cancel: CancellationToken, + ) -> LazyFieldsTask { + Box::pin(async move { Ok(Vec::new()) }) + } +} + +impl ParserDescriptor for Descriptor { + fn get_render(&self) -> Option { + None + } +} diff --git a/application/apps/indexer/parsers/src/dlt/raw/mod.rs b/application/apps/indexer/parsers/src/dlt/raw/mod.rs new file mode 100644 index 000000000..39066821a --- /dev/null +++ b/application/apps/indexer/parsers/src/dlt/raw/mod.rs @@ -0,0 +1,47 @@ +pub mod descriptor; + +use super::{MIN_MSG_LEN, map_dlt_err}; +use crate::*; +pub use descriptor::*; +use dlt_core::parse::dlt_consume_msg; +pub use dlt_core::{ + dlt::LogLevel, + fibex::{FibexConfig, FibexMetadata as FibexDltMetadata, gather_fibex_data}, + filtering::{DltFilterConfig, ProcessedDltFilterConfig}, +}; + +pub struct DltRawParser { + pub with_storage_header: bool, +} + +impl DltRawParser { + pub fn new(with_storage_header: bool) -> Self { + Self { + with_storage_header, + } + } + + fn parse_item<'a>( + &mut self, + input: &'a [u8], + _timestamp: Option, + ) -> Result<(usize, Option<&'a [u8]>), ParserError> { + let (rest, consumed) = dlt_consume_msg(input).map_err(map_dlt_err)?; + let msg = consumed.map(|c| &input[0..c as usize]); + let total_consumed = input.len() - rest.len(); + Ok((total_consumed, msg)) + } +} + +impl SingleParser for DltRawParser { + const MIN_MSG_LEN: usize = MIN_MSG_LEN; + + fn parse_item<'a>( + &mut self, + input: &'a [u8], + timestamp: Option, + ) -> Result<(usize, Option>), ParserError> { + let (consumed, data) = self.parse_item(input, timestamp)?; + Ok((consumed, data.map(LogRecordOutput::Raw))) + } +} diff --git a/application/apps/indexer/parsers/src/lib.rs b/application/apps/indexer/parsers/src/lib.rs index 36ecf6adc..e0e474d86 100644 --- a/application/apps/indexer/parsers/src/lib.rs +++ b/application/apps/indexer/parsers/src/lib.rs @@ -1,173 +1,41 @@ -#![deny(unused_crate_dependencies)] +// #![deny(unused_crate_dependencies)] + +pub mod api; pub mod dlt; +pub mod prelude; pub mod someip; pub mod text; -use serde::Serialize; -use std::{ - fmt::{Debug, Display}, - io::Write, - iter, -}; -use thiserror::Error; - -extern crate log; -#[derive(Error, Debug)] -pub enum Error { - #[error("Unrecoverable error, cannot continue: {0}")] - Unrecoverable(String), - #[error("Parse error: {0}")] - Parse(String), - #[error("Incomplete, not enough data for a message")] - Incomplete, - #[error("End of file reached")] - Eof, -} - -#[derive(Debug)] -pub enum ParseYield { - Message(T), - Attachment(Attachment), - MessageAndAttachment((T, Attachment)), -} - -impl From for ParseYield { - fn from(item: T) -> Self { - Self::Message(item) +pub use api::*; + +pub enum Parsers { + DltRaw(dlt::raw::DltRawParser), + Dlt(dlt::DltParser), + SomeIp(someip::SomeipParser), + Text(text::StringTokenizer), + // NOTE: We can't reference plugins parser directly because of circular + // references between parsers and plugins_host library. + // TODO AAZ: This is a workaround until we find a proper solution. + Plugin(Box), +} + +/** + * NOTE/TODO: + * Using of the whole enum inside MessageProducer might give performance impact because it's massive. + * Into MessageProducer we should put exact instances of parsers instead enum-wrapper + */ + +impl Parser for Parsers { + fn parse<'a>(&'a mut self, input: &'a [u8], timestamp: Option) -> ParseReturnIterator<'a> { + match self { + Self::Dlt(inst) => inst.parse(input, timestamp), + Self::DltRaw(inst) => inst.parse(input, timestamp), + Self::SomeIp(inst) => inst.parse(input, timestamp), + Self::Text(inst) => inst.parse(input, timestamp), + Self::Plugin(inst) => inst.parse(input, timestamp), + } } } -/// Parser trait that needs to be implemented for any parser we support -/// in chipmunk -pub trait Parser { - /// Takes a slice of bytes and try to apply a parser. If it can parse any item of them, - /// it will return iterator of items each with the consumed bytes count along with `Some(log_message)` - /// - /// if the slice does not have enough bytes to parse any item, an [`Error`] is returned. - /// - /// in case we could parse a message but the message was filtered out, `None` is returned on - /// that item. - /// - /// # Note: - /// - /// If the parsers encounter any error while it already has parsed any items, then it must - /// return those items without the error, then on the next call it can return the errors in - /// case it was provided with the same slice of bytes. - fn parse( - &mut self, - input: &[u8], - timestamp: Option, - ) -> Result>)>, Error>; -} - -#[derive(Debug, Clone, Serialize)] -pub struct Attachment { - pub name: String, - pub size: usize, - pub created_date: Option, - pub modified_date: Option, - /// The indexes of the message within the original trace (0-based). - pub messages: Vec, - pub data: Vec, -} - -impl Attachment { - pub fn add_data(&mut self, new_data: &[u8]) { - self.data.extend_from_slice(new_data); - } -} - -pub trait Collector { - fn register_message(&mut self, offset: usize, msg: &T); - fn attachment_indexes(&self) -> Vec; -} - -pub trait LineFormat { - fn format_line(&self) -> String; -} - -pub enum ByteRepresentation { - Owned(Vec), - Range((usize, usize)), -} - -pub trait LogMessage: Display + Serialize { - /// Serializes a message directly into a Writer - /// returns the size of the serialized message - fn to_writer(&self, writer: &mut W) -> Result; -} - -#[derive(Debug)] -pub enum MessageStreamItem { - Item(ParseYield), - Skipped, - Incomplete, - Empty, - Done, -} - -/// A trait for parsers that extract one item at a time from a byte slice. -/// -/// Any type implementing this trait will automatically implement the [`Parser`] trait -/// due to a blanket implementation. This means that such types will support extracting -/// all available items from an input slice by repeatedly calling [`SingleParser::parse_item()`] -/// until no more items can be parsed. -/// -/// # Behavior -/// -/// - The blanket implementation of [`Parser`] will repeatedly invoke `parse_item()`, -/// extracting as many items as possible. -/// - If `parse_item()` fails on the first call, an error is returned immediately. -/// - If `parse_item()` succeeds, parsing continues until: -/// - The remaining input is too short to parse another item. -/// - `parse_item()` returns an error, which is ignored after the first successful parse. -pub trait SingleParser { - /// The minimum number of bytes required to parse an item. - /// - /// # Notes: - /// - This value is used to prevent unnecessary parsing attempts when the remaining input - /// is too short to contain a valid message. - /// - The default value (`1`) indicates that the parser has no minimum length requirement. - const MIN_MSG_LEN: usize = 1; - - /// Parses a single item from the given byte slice. - /// - /// in case we could parse a message but the message was filtered out, `None` is returned on - /// that item. - fn parse_item( - &mut self, - input: &[u8], - timestamp: Option, - ) -> Result<(usize, Option>), Error>; -} - -/// This blanket implementation repeatedly applies [`SingleParser::parse_item()`] function, -/// extracting as many items as possible from the provided input until no more can be parsed. -impl Parser for P -where - P: SingleParser, -{ - fn parse( - &mut self, - input: &[u8], - timestamp: Option, - ) -> Result>)>, Error> { - let mut slice = input; - - // return early if function errors on first parse call. - let first_res = self.parse_item(slice, timestamp)?; - - // Otherwise keep parsing and stop on first error, returning the parsed items at the end. - let iter = iter::successors(Some(first_res), move |(consumed, _res)| { - slice = &slice[*consumed..]; - - if slice.len() < P::MIN_MSG_LEN { - return None; - } - - self.parse_item(slice, timestamp).ok() - }); - - Ok(iter) - } -} +// TODO: this registration function will fail if some of parser would not be registred. That's wrong. +// If some of parsers are failed, another parsers should still be registred as well diff --git a/application/apps/indexer/parsers/src/prelude.rs b/application/apps/indexer/parsers/src/prelude.rs new file mode 100644 index 000000000..5336c3ed7 --- /dev/null +++ b/application/apps/indexer/parsers/src/prelude.rs @@ -0,0 +1 @@ +pub use crate::{dlt::DltParser, someip::SomeipParser, text::StringTokenizer}; diff --git a/application/apps/indexer/parsers/src/someip/descriptor.rs b/application/apps/indexer/parsers/src/someip/descriptor.rs new file mode 100644 index 000000000..9d74e78db --- /dev/null +++ b/application/apps/indexer/parsers/src/someip/descriptor.rs @@ -0,0 +1,140 @@ +use crate::{Parsers, someip::FibexMetadata}; +use ::descriptor::{CommonDescriptor, FieldsResult, ParserDescriptor}; +use descriptor::ParserFactory; +use std::path::PathBuf; +use stypes::{ + ComponentOptions, ExtractByKey, Field, FieldDesc, NativeError, NativeErrorKind, SessionAction, + Severity, StaticFieldDesc, Value, ValueInput, missed_field_err as missed, +}; + +use super::SomeipParser; + +const SOMEIP_PARSER_UUID: uuid::Uuid = uuid::Uuid::from_bytes([ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, +]); + +// TODO: fields IDs could be same for diff parser/source... on level of Options event +// we should also provide a master of field to prevent conflicts. +const FIELD_FIBEX_FILES: &str = "SOMEIP_PARSER_FIELD_FIBEX_FILES"; +const FIELD_TZ: &str = "SOMEIP_PARSER_FIELD_TIMEZONE"; + +#[derive(Default)] +pub struct Descriptor {} + +impl ParserFactory for Descriptor { + fn create( + &self, + origin: &stypes::SessionAction, + options: &[stypes::Field], + ) -> Result)>, stypes::NativeError> { + let errors = self.validate(origin, options)?; + if !errors.is_empty() { + return Err(NativeError { + kind: NativeErrorKind::Configuration, + severity: Severity::ERROR, + message: Some( + errors + .values() + .map(String::as_str) + .collect::>() + .join("; "), + ), + }); + } + let fibex_file_paths: &Vec = options + .extract_by_key(FIELD_FIBEX_FILES) + .ok_or(missed(FIELD_FIBEX_FILES))? + .value; + Ok(Some(( + crate::Parsers::SomeIp(SomeipParser { + fibex_metadata: FibexMetadata::from_fibex_files(fibex_file_paths), + }), + Some("SomeIp".to_owned()), + ))) + } +} + +impl CommonDescriptor for Descriptor { + fn is_compatible(&self, origin: &SessionAction) -> bool { + let files = match origin { + SessionAction::File(filepath) => { + vec![filepath] + } + SessionAction::Files(files) => files.iter().collect(), + SessionAction::Source => return true, + SessionAction::ExportRaw(..) => return false, + }; + files.iter().any(|fp| { + fp.extension() + .map(|ext| { + ["dlt", "pcap", "pcapng"] + .contains(&ext.to_string_lossy().to_lowercase().as_str()) + }) + .unwrap_or_default() + }) + } + fn fields_getter(&self, _origin: &SessionAction) -> FieldsResult { + Ok(vec![ + FieldDesc::Static(StaticFieldDesc { + id: FIELD_FIBEX_FILES.to_owned(), + name: "Fibex Files".to_owned(), + desc: "Fibex Files - allows the user to attach one or more XML files containing the schema definitions for decoding the payload. These files are used to interpret the payload contents according to the provided descriptions. If decoding fails or no matching schema is found, the payload will be displayed as-is, in raw hexadecimal form.".to_owned(), + required: true, + interface: ValueInput::Files(vec!["xml".to_owned(), "*".to_owned()]), + binding: None, + }), + FieldDesc::Static(StaticFieldDesc { + id: FIELD_TZ.to_owned(), + name: "Timezone".to_owned(), + desc: "Timezone - a display-only setting used for visualizing timestamps. If a timezone is specified, all timestamps will be adjusted accordingly to improve readability. This setting does not modify the underlying data and is used purely for presentation purposes.".to_owned(), + required: true, + interface: ValueInput::Timezone, + binding: None, + }), + ]) + } + + fn ident(&self) -> stypes::Ident { + stypes::Ident { + name: String::from("SOME/IP"), + desc: String::from( + "SomeIP Parser is a binary parser designed to decode messages conforming to the AUTOSAR SOME/IP (Scalable service-Oriented Middleware over IP) protocol. It processes raw binary input and extracts structured message data, including service ID, method ID, payload length, and more. The parser can be applied to both files and network streams (e.g., TCP/UDP). If a valid DLT message is detected within the SOME/IP payload, it will be automatically passed to the DLT parser and decoded accordingly.", + ), + io: stypes::IODataType::NetworkFramePayload, + uuid: SOMEIP_PARSER_UUID, + } + } + fn get_default_options(&self, origin: &stypes::SessionAction) -> Option> { + Some(vec![Field { + id: FIELD_FIBEX_FILES.to_owned(), + value: Value::Files(Vec::new()), + }]) + } +} + +impl ParserDescriptor for Descriptor { + fn get_render(&self) -> Option { + Some(stypes::OutputRender::Columns(vec![ + ("SOME/IP".to_owned(), 50), + ("SERV".to_owned(), 50), + ("METH".to_owned(), 50), + ("LENG".to_owned(), 30), + ("CLID".to_owned(), 30), + ("SEID".to_owned(), 30), + ("IVER".to_owned(), 30), + ("MSTP".to_owned(), 30), + ("RETC".to_owned(), 30), + ("PAYLOAD".to_owned(), 0), + ])) + } +} + +pub fn get_default_options(fibex: Option>) -> ComponentOptions { + ComponentOptions { + uuid: SOMEIP_PARSER_UUID, + fields: vec![Field { + id: FIELD_FIBEX_FILES.to_owned(), + value: Value::Files(fibex.unwrap_or_default()), + }], + } +} diff --git a/application/apps/indexer/parsers/src/someip.rs b/application/apps/indexer/parsers/src/someip/mod.rs similarity index 68% rename from application/apps/indexer/parsers/src/someip.rs rename to application/apps/indexer/parsers/src/someip/mod.rs index 4d71b02d1..b5ddf6948 100644 --- a/application/apps/indexer/parsers/src/someip.rs +++ b/application/apps/indexer/parsers/src/someip/mod.rs @@ -1,26 +1,24 @@ -use crate::{Error, LogMessage, ParseYield, SingleParser}; +pub mod descriptor; +use crate::*; +use lazy_static::lazy_static; +use log::{debug, error}; +use regex::Regex; +use serde::Serialize; +use someip_messages::*; +use someip_payload::{ + fibex::{FibexModel, FibexParser, FibexReader, FibexServiceInterface, FibexTypeDeclaration}, + fibex2som::FibexTypes, + som::{SOMParser, SOMType}, +}; use std::{ borrow::Cow, cmp::Ordering, collections::{HashMap, hash_map::Entry}, fmt::{self, Display}, - io::Write, path::PathBuf, sync::Mutex, }; -use someip_messages::*; -use someip_payload::{ - fibex::{FibexModel, FibexParser, FibexReader, FibexServiceInterface, FibexTypeDeclaration}, - fibex2som::FibexTypes, - som::{SOMParser, SOMType}, -}; - -use lazy_static::lazy_static; -use log::{debug, error}; -use regex::Regex; -use serde::Serialize; - /// Marker for a column separator in the output string. const COLUMN_SEP: &str = "\u{0004}"; // EOT /// Marker for a newline in the output string. @@ -38,7 +36,7 @@ pub struct FibexMetadata { impl FibexMetadata { /// Returns a new meta-data from the given fibex-files. - pub fn from_fibex_files(paths: Vec) -> Option { + pub fn from_fibex_files(paths: &[PathBuf]) -> Option { let readers: Vec<_> = paths .iter() .filter_map(|path| FibexReader::from_file(path).ok()) @@ -194,7 +192,7 @@ impl SomeipParser { } /// Creates a new parser with the given files. - pub fn from_fibex_files(paths: Vec) -> Self { + pub fn from_fibex_files(paths: &[PathBuf]) -> Self { SomeipParser { fibex_metadata: FibexMetadata::from_fibex_files(paths), } @@ -205,7 +203,7 @@ impl SomeipParser { fibex_metadata: Option<&FibexMetadata>, input: &[u8], timestamp: Option, - ) -> Result<(usize, SomeipLogMessage), Error> { + ) -> Result<(usize, SomeipLogMessage), ParserError> { let time = timestamp.unwrap_or(0); match Message::from_slice(input) { Ok(Message::Sd(header, payload)) => { @@ -273,30 +271,42 @@ impl SomeipParser { } Err(e) => match e { - someip_messages::Error::NotEnoughData { .. } => Err(Error::Incomplete), + someip_messages::Error::NotEnoughData { .. } => Err(ParserError::Incomplete), e => { let msg = e.to_string(); error!("at {time} : {msg}"); - Err(Error::Parse(msg)) + Err(ParserError::Parse(msg)) } }, } } + + fn parse_item( + &mut self, + input: &[u8], + timestamp: Option, + ) -> Result<(usize, Option), ParserError> { + SomeipParser::parse_message(self.fibex_metadata.as_ref(), input, timestamp) + .map(|(rest, message)| (rest, Some(message))) + } } unsafe impl Send for SomeipParser {} unsafe impl Sync for SomeipParser {} -impl SingleParser for SomeipParser { +impl SingleParser for SomeipParser { const MIN_MSG_LEN: usize = MIN_MSG_LEN; - fn parse_item( + fn parse_item<'a>( &mut self, - input: &[u8], + input: &'a [u8], timestamp: Option, - ) -> Result<(usize, Option>), Error> { - SomeipParser::parse_message(self.fibex_metadata.as_ref(), input, timestamp) - .map(|(rest, message)| (rest, Some(ParseYield::from(message)))) + ) -> Result<(usize, Option>), ParserError> { + let (consumed, data) = self.parse_item(input, timestamp)?; + Ok(( + consumed, + data.map(|msg| LogRecordOutput::Message(msg.to_string().into())), + )) } } @@ -496,12 +506,12 @@ impl SomeipLogMessage { } } -impl LogMessage for SomeipLogMessage { - fn to_writer(&self, writer: &mut W) -> Result { - writer.write_all(&self.bytes)?; - Ok(self.bytes.len()) - } -} +// impl LogMessage for SomeipLogMessage { +// fn to_writer(&self, writer: &mut W) -> Result { +// writer.write_all(&self.bytes)?; +// Ok(self.bytes.len()) +// } +// } impl Display for SomeipLogMessage { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -533,49 +543,49 @@ mod test { fn test_metadata() -> FibexMetadata { let xml = r#" - - TestService - 259 - - - 1 - 2 - - - - EmptyEvent - 32772 - FIRE_AND_FORGET - - - TestEvent - 32773 - FIRE_AND_FORGET - - - Value1 - - - false - - 0 - - - Value2 - - - false - - 1 - - - - - - - UINT8 - - "#; + + TestService + 259 + + + 1 + 2 + + + + EmptyEvent + 32772 + FIRE_AND_FORGET + + + TestEvent + 32773 + FIRE_AND_FORGET + + + Value1 + + + false + + 0 + + + Value2 + + + false + + 1 + + + + + + + UINT8 + + "#; FibexMetadata::new( FibexParser::parse(vec![ @@ -590,10 +600,10 @@ mod test { let input: &[u8] = &[]; let mut parser = SomeipParser::new(); - let result = parser.parse_item(input, None); + let result = parser.parse(input, None); match result { - Err(crate::Error::Incomplete) => {} + Err(ParserError::Incomplete) => {} Err(err) => panic!("unexpected error: {err}"), _ => panic!("unexpected parse result"), } @@ -609,10 +619,10 @@ mod test { ]; let mut parser = SomeipParser::new(); - let result = parser.parse_item(input, None); + let result = parser.parse(input, None); match result { - Err(crate::Error::Incomplete) => {} + Err(ParserError::Incomplete) => {} Err(err) => panic!("unexpected error: {err}"), _ => panic!("unexpected parse result"), } @@ -629,15 +639,12 @@ mod test { let mut parser = SomeipParser::new(); - let (consumed, message) = parser.parse_item(input, None).unwrap(); + let mut items: Vec<_> = parser.parse(input, None).unwrap().collect(); + assert_eq!(items.len(), 1); + let (consumed, message) = items.pop().unwrap(); assert_eq!(consumed, input.len()); - if let ParseYield::Message(item) = message.unwrap() { - assert_eq!("MCC", &format!("{}", item)); - assert_eq!("MCC", &format!("{:?}", item)); - } else { - panic!("unexpected parse yield"); - } + assert_eq!("MCC", &format!("{}", message.unwrap())); } #[test] @@ -651,15 +658,12 @@ mod test { let mut parser = SomeipParser::new(); - let (consumed, message) = parser.parse_item(input, None).unwrap(); + let mut items: Vec<_> = parser.parse(input, None).unwrap().collect(); + assert_eq!(items.len(), 1); + let (consumed, message) = items.pop().unwrap(); assert_eq!(consumed, input.len()); - if let ParseYield::Message(item) = message.unwrap() { - assert_eq!("MCS", &format!("{}", item)); - assert_eq!("MCS", &format!("{:?}", item)); - } else { - panic!("unexpected parse yield"); - } + assert_eq!("MCS", &format!("{}", message.unwrap())); } #[test] @@ -673,21 +677,15 @@ mod test { let mut parser = SomeipParser::new(); - let (consumed, message) = parser.parse_item(input, None).unwrap(); + let mut items: Vec<_> = parser.parse(input, None).unwrap().collect(); + assert_eq!(items.len(), 1); + let (consumed, message) = items.pop().unwrap(); assert_eq!(consumed, input.len()); - if let ParseYield::Message(item) = message.unwrap() { - assert_eq!( - "RPC\u{4}259\u{4}32772\u{4}8\u{4}1\u{4}2\u{4}1\u{4}2\u{4}0\u{4}[]", - &format!("{}", item) - ); - assert_eq!( - "RPC SERV:259 METH:32772 LENG:8 CLID:1 SEID:2 IVER:1 MSTP:2 RETC:0 []", - &format!("{:?}", item) - ); - } else { - panic!("unexpected parse yield"); - } + assert_eq!( + "RPC\u{4}259\u{4}32772\u{4}8\u{4}1\u{4}2\u{4}1\u{4}2\u{4}0\u{4}[]", + &format!("{}", message.unwrap()) + ); } #[test] @@ -704,21 +702,15 @@ mod test { fibex_metadata: Some(fibex_metadata), }; - let (consumed, message) = parser.parse_item(input, None).unwrap(); + let mut items: Vec<_> = parser.parse(input, None).unwrap().collect(); + assert_eq!(items.len(), 1); + let (consumed, message) = items.pop().unwrap(); assert_eq!(consumed, input.len()); - if let ParseYield::Message(item) = message.unwrap() { - assert_eq!( - "RPC\u{4}259\u{4}32772\u{4}8\u{4}1\u{4}2\u{4}1\u{4}2\u{4}0\u{4}TestService::emptyEvent ", - &format!("{}", item) - ); - assert_eq!( - "RPC SERV:259 METH:32772 LENG:8 CLID:1 SEID:2 IVER:1 MSTP:2 RETC:0 TestService::emptyEvent ", - &format!("{:?}", item) - ); - } else { - panic!("unexpected parse yield"); - } + assert_eq!( + "RPC\u{4}259\u{4}32772\u{4}8\u{4}1\u{4}2\u{4}1\u{4}2\u{4}0\u{4}TestService::emptyEvent ", + &format!("{}", message.unwrap()) + ); } #[test] @@ -733,21 +725,15 @@ mod test { let mut parser = SomeipParser::new(); - let (consumed, message) = parser.parse_item(input, None).unwrap(); + let mut items: Vec<_> = parser.parse(input, None).unwrap().collect(); + assert_eq!(items.len(), 1); + let (consumed, message) = items.pop().unwrap(); assert_eq!(consumed, input.len()); - if let ParseYield::Message(item) = message.unwrap() { - assert_eq!( - "RPC\u{4}259\u{4}32773\u{4}10\u{4}1\u{4}2\u{4}1\u{4}2\u{4}0\u{4}[01, 02]", - &format!("{}", item) - ); - assert_eq!( - "RPC SERV:259 METH:32773 LENG:10 CLID:1 SEID:2 IVER:1 MSTP:2 RETC:0 [01, 02]", - &format!("{:?}", item) - ); - } else { - panic!("unexpected parse yield"); - } + assert_eq!( + "RPC\u{4}259\u{4}32773\u{4}10\u{4}1\u{4}2\u{4}1\u{4}2\u{4}0\u{4}[01, 02]", + &format!("{}", message.unwrap()) + ); } #[test] @@ -765,21 +751,15 @@ mod test { fibex_metadata: Some(fibex_metadata), }; - let (consumed, message) = parser.parse_item(input, None).unwrap(); + let mut items: Vec<_> = parser.parse(input, None).unwrap().collect(); + assert_eq!(items.len(), 1); + let (consumed, message) = items.pop().unwrap(); assert_eq!(consumed, input.len()); - if let ParseYield::Message(item) = message.unwrap() { - assert_eq!( - "RPC\u{4}259\u{4}32773\u{4}10\u{4}1\u{4}2\u{4}1\u{4}2\u{4}0\u{4}TestService::testEvent {\u{6}\tvalue1 (UINT8) : 1,\u{6}\tvalue2 (UINT8) : 2,\u{6}}", - &format!("{}", item) - ); - assert_eq!( - "RPC SERV:259 METH:32773 LENG:10 CLID:1 SEID:2 IVER:1 MSTP:2 RETC:0 TestService::testEvent {\u{6}\tvalue1 (UINT8) : 1,\u{6}\tvalue2 (UINT8) : 2,\u{6}}", - &format!("{:?}", item) - ); - } else { - panic!("unexpected parse yield"); - } + assert_eq!( + "RPC\u{4}259\u{4}32773\u{4}10\u{4}1\u{4}2\u{4}1\u{4}2\u{4}0\u{4}TestService::testEvent {\u{6}\tvalue1 (UINT8) : 1,\u{6}\tvalue2 (UINT8) : 2,\u{6}}", + &format!("{}", message.unwrap()) + ); } #[test] @@ -797,21 +777,15 @@ mod test { fibex_metadata: Some(fibex_metadata), }; - let (consumed, message) = parser.parse_item(input, None).unwrap(); + let mut items: Vec<_> = parser.parse(input, None).unwrap().collect(); + assert_eq!(items.len(), 1); + let (consumed, message) = items.pop().unwrap(); assert_eq!(consumed, input.len()); - if let ParseYield::Message(item) = message.unwrap() { - assert_eq!( - "RPC\u{4}260\u{4}32773\u{4}10\u{4}1\u{4}2\u{4}1\u{4}2\u{4}0\u{4}UnknownService [01, 02]", - &format!("{}", item) - ); - assert_eq!( - "RPC SERV:260 METH:32773 LENG:10 CLID:1 SEID:2 IVER:1 MSTP:2 RETC:0 UnknownService [01, 02]", - &format!("{:?}", item) - ); - } else { - panic!("unexpected parse yield"); - } + assert_eq!( + "RPC\u{4}260\u{4}32773\u{4}10\u{4}1\u{4}2\u{4}1\u{4}2\u{4}0\u{4}UnknownService [01, 02]", + &format!("{}", message.unwrap()) + ); } #[test] @@ -829,21 +803,15 @@ mod test { fibex_metadata: Some(fibex_metadata), }; - let (consumed, message) = parser.parse_item(input, None).unwrap(); + let mut items: Vec<_> = parser.parse(input, None).unwrap().collect(); + assert_eq!(items.len(), 1); + let (consumed, message) = items.pop().unwrap(); assert_eq!(consumed, input.len()); - if let ParseYield::Message(item) = message.unwrap() { - assert_eq!( - "RPC\u{4}259\u{4}32773\u{4}10\u{4}1\u{4}2\u{4}3\u{4}2\u{4}0\u{4}TestService<1?>::testEvent {\u{6}\tvalue1 (UINT8) : 1,\u{6}\tvalue2 (UINT8) : 2,\u{6}}", - &format!("{}", item) - ); - assert_eq!( - "RPC SERV:259 METH:32773 LENG:10 CLID:1 SEID:2 IVER:3 MSTP:2 RETC:0 TestService<1?>::testEvent {\u{6}\tvalue1 (UINT8) : 1,\u{6}\tvalue2 (UINT8) : 2,\u{6}}", - &format!("{:?}", item) - ); - } else { - panic!("unexpected parse yield"); - } + assert_eq!( + "RPC\u{4}259\u{4}32773\u{4}10\u{4}1\u{4}2\u{4}3\u{4}2\u{4}0\u{4}TestService<1?>::testEvent {\u{6}\tvalue1 (UINT8) : 1,\u{6}\tvalue2 (UINT8) : 2,\u{6}}", + &format!("{}", message.unwrap()) + ); } #[test] @@ -861,21 +829,15 @@ mod test { fibex_metadata: Some(fibex_metadata), }; - let (consumed, message) = parser.parse_item(input, None).unwrap(); + let mut items: Vec<_> = parser.parse(input, None).unwrap().collect(); + assert_eq!(items.len(), 1); + let (consumed, message) = items.pop().unwrap(); assert_eq!(consumed, input.len()); - if let ParseYield::Message(item) = message.unwrap() { - assert_eq!( - "RPC\u{4}259\u{4}32774\u{4}10\u{4}1\u{4}2\u{4}1\u{4}2\u{4}0\u{4}TestService::UnknownMethod [01, 02]", - &format!("{}", item) - ); - assert_eq!( - "RPC SERV:259 METH:32774 LENG:10 CLID:1 SEID:2 IVER:1 MSTP:2 RETC:0 TestService::UnknownMethod [01, 02]", - &format!("{:?}", item) - ); - } else { - panic!("unexpected parse yield"); - } + assert_eq!( + "RPC\u{4}259\u{4}32774\u{4}10\u{4}1\u{4}2\u{4}1\u{4}2\u{4}0\u{4}TestService::UnknownMethod [01, 02]", + &format!("{}", message.unwrap()) + ); } #[test] @@ -893,21 +855,15 @@ mod test { fibex_metadata: Some(fibex_metadata), }; - let (consumed, message) = parser.parse_item(input, None).unwrap(); + let mut items: Vec<_> = parser.parse(input, None).unwrap().collect(); + assert_eq!(items.len(), 1); + let (consumed, message) = items.pop().unwrap(); assert_eq!(consumed, input.len()); - if let ParseYield::Message(item) = message.unwrap() { - assert_eq!( - "RPC\u{4}259\u{4}32773\u{4}9\u{4}1\u{4}2\u{4}1\u{4}2\u{4}0\u{4}TestService::testEvent 'SOME/IP Error: Parser exhausted at offset 1 for Object size 1' [01]", - &format!("{}", item) - ); - assert_eq!( - "RPC SERV:259 METH:32773 LENG:9 CLID:1 SEID:2 IVER:1 MSTP:2 RETC:0 TestService::testEvent 'SOME/IP Error: Parser exhausted at offset 1 for Object size 1' [01]", - &format!("{:?}", item) - ); - } else { - panic!("unexpected parse yield"); - } + assert_eq!( + "RPC\u{4}259\u{4}32773\u{4}9\u{4}1\u{4}2\u{4}1\u{4}2\u{4}0\u{4}TestService::testEvent 'SOME/IP Error: Parser exhausted at offset 1 for Object size 1' [01]", + &format!("{}", message.unwrap()) + ); } #[test] @@ -923,21 +879,16 @@ mod test { ]; let mut parser = SomeipParser::new(); - let (consumed, message) = parser.parse_item(input, None).unwrap(); + let mut items: Vec<_> = parser.parse(input, None).unwrap().collect(); + assert_eq!(items.len(), 1); + + let (consumed, message) = items.pop().unwrap(); assert_eq!(consumed, input.len()); - if let ParseYield::Message(item) = message.unwrap() { - assert_eq!( - "SD\u{4}65535\u{4}33024\u{4}20\u{4}0\u{4}0\u{4}1\u{4}2\u{4}0\u{4}Flags [C0]", - &format!("{}", item) - ); - assert_eq!( - "SD SERV:65535 METH:33024 LENG:20 CLID:0 SEID:0 IVER:1 MSTP:2 RETC:0 Flags [C0]", - &format!("{:?}", item) - ); - } else { - panic!("unexpected parse yield"); - } + assert_eq!( + "SD\u{4}65535\u{4}33024\u{4}20\u{4}0\u{4}0\u{4}1\u{4}2\u{4}0\u{4}Flags [C0]", + &format!("{}", message.unwrap()) + ); } #[test] @@ -970,67 +921,62 @@ mod test { let mut parser = SomeipParser::new(); - let (consumed, message) = parser.parse_item(input, None).unwrap(); + let mut items: Vec<_> = parser.parse(input, None).unwrap().collect(); + assert_eq!(items.len(), 1); + + let (consumed, message) = items.pop().unwrap(); assert_eq!(consumed, input.len()); - if let ParseYield::Message(item) = message.unwrap() { - assert_eq!( - "SD\u{4}65535\u{4}33024\u{4}64\u{4}0\u{4}0\u{4}1\u{4}2\u{4}0\u{4}Flags [C0], Subscribe 259-456 v2 Inst 1 Ttl 3, Subscribe-Ack 259-456 v2 Inst 1 Ttl 3 UDP 127.0.0.1:30000", - &format!("{}", item) - ); - assert_eq!( - "SD SERV:65535 METH:33024 LENG:64 CLID:0 SEID:0 IVER:1 MSTP:2 RETC:0 Flags [C0], Subscribe 259-456 v2 Inst 1 Ttl 3, Subscribe-Ack 259-456 v2 Inst 1 Ttl 3 UDP 127.0.0.1:30000", - &format!("{:?}", item) - ); - } else { - panic!("unexpected parse yield"); - } + assert_eq!( + "SD\u{4}65535\u{4}33024\u{4}64\u{4}0\u{4}0\u{4}1\u{4}2\u{4}0\u{4}Flags [C0], Subscribe 259-456 v2 Inst 1 Ttl 3, Subscribe-Ack 259-456 v2 Inst 1 Ttl 3 UDP 127.0.0.1:30000", + &format!("{}", message.unwrap()) + ); } #[test] fn service_lookup() { let xml = r#" - - Foo - 123 - - 1 - 0 - - - - Foo - 321 - - 1 - 0 - - - - Foo - 123 - - 2 - 1 - - - - Foo - 123 - - 1 - 1 - - - - Foo - 123 - - 2 - 3 - - - "#; + + Foo + 123 + + 1 + 0 + + + + Foo + 321 + + 1 + 0 + + + + Foo + 123 + + 2 + 1 + + + + Foo + 123 + + 1 + 1 + + + + Foo + 123 + + 2 + 3 + + + "#; let meta_data = FibexMetadata::new( FibexParser::parse(vec![ diff --git a/application/apps/indexer/parsers/src/text/descriptor.rs b/application/apps/indexer/parsers/src/text/descriptor.rs new file mode 100644 index 000000000..780326642 --- /dev/null +++ b/application/apps/indexer/parsers/src/text/descriptor.rs @@ -0,0 +1,67 @@ +use descriptor::{CommonDescriptor, ParserDescriptor, ParserFactory}; +use file_tools::is_binary; +use stypes::{ComponentOptions, SessionAction}; + +use crate::Parsers; + +use super::StringTokenizer; + +const TEXT_PARSER_UUID: uuid::Uuid = uuid::Uuid::from_bytes([ + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, +]); + +#[derive(Default)] +pub struct Descriptor {} + +impl ParserFactory for Descriptor { + fn create( + &self, + _origin: &stypes::SessionAction, + _options: &[stypes::Field], + ) -> Result)>, stypes::NativeError> { + Ok(Some((crate::Parsers::Text(StringTokenizer {}), None))) + } +} + +impl CommonDescriptor for Descriptor { + fn is_compatible(&self, origin: &stypes::SessionAction) -> bool { + let files = match origin { + SessionAction::File(filepath) => { + vec![filepath] + } + SessionAction::Files(files) => files.iter().collect(), + SessionAction::Source => return true, + SessionAction::ExportRaw(..) => return false, + }; + // If at least some file doesn't exist or it's binary - do not recommend this source + !files + .into_iter() + .any(|f| !f.exists() || is_binary(f.to_string_lossy().to_string()).unwrap_or_default()) + } + fn ident(&self) -> stypes::Ident { + stypes::Ident { + name: String::from("Text Parser"), + desc: String::from( + "Text Parser is a minimal parser designed for processing plain text input. It performs no decoding or transformation and has no configuration options. Its sole purpose is to output incoming data line by line, making it suitable for logs or command outputs in textual format.", + ), + io: stypes::IODataType::PlaitText, + uuid: TEXT_PARSER_UUID, + } + } + fn get_default_options(&self, origin: &stypes::SessionAction) -> Option> { + Some(vec![]) + } +} + +impl ParserDescriptor for Descriptor { + fn get_render(&self) -> Option { + Some(stypes::OutputRender::PlaitText) + } +} + +pub fn get_default_options() -> ComponentOptions { + ComponentOptions { + uuid: TEXT_PARSER_UUID, + fields: Vec::new(), + } +} diff --git a/application/apps/indexer/parsers/src/text.rs b/application/apps/indexer/parsers/src/text/mod.rs similarity index 52% rename from application/apps/indexer/parsers/src/text.rs rename to application/apps/indexer/parsers/src/text/mod.rs index 689cf2094..ada730bef 100644 --- a/application/apps/indexer/parsers/src/text.rs +++ b/application/apps/indexer/parsers/src/text/mod.rs @@ -1,6 +1,11 @@ -use crate::{Error, LogMessage, ParseYield, SingleParser}; +pub mod descriptor; + +use crate::*; use serde::Serialize; -use std::{fmt, io::Write}; +use std::borrow::Cow; + +/// The most likely minimal bytes count needed to parse a text message. +const MIN_MSG_LEN: usize = 1; pub struct StringTokenizer {} @@ -9,29 +14,12 @@ pub struct StringMessage { content: String, } -impl fmt::Display for StringMessage { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.content) - } -} - -impl LogMessage for StringMessage { - fn to_writer(&self, writer: &mut W) -> Result { - let len = self.content.len(); - writer.write_all(self.content.as_bytes())?; - Ok(len) - } -} - -impl SingleParser for StringTokenizer -where - StringMessage: LogMessage, -{ - fn parse_item( +impl StringTokenizer { + fn parse_item<'a>( &mut self, - input: &[u8], + input: &'a [u8], _timestamp: Option, - ) -> Result<(usize, Option>), Error> { + ) -> Result<(usize, Option>), ParserError> { // TODO: support non-utf8 encodings use memchr::memchr; if input.is_empty() { @@ -39,47 +27,50 @@ where } let item = if let Some(msg_size) = memchr(b'\n', input) { let content = String::from_utf8_lossy(&input[..msg_size]); - let string_msg = StringMessage { - content: content.to_string(), - }; - (msg_size + 1, Some(string_msg.into())) + (msg_size + 1, Some(content)) } else { - ( - input.len(), - Some(ParseYield::from(StringMessage { - content: String::new(), - })), - ) + (input.len(), Some(Cow::Borrowed(""))) }; Ok(item) } } +impl SingleParser for StringTokenizer { + const MIN_MSG_LEN: usize = MIN_MSG_LEN; + + fn parse_item<'a>( + &mut self, + input: &'a [u8], + timestamp: Option, + ) -> Result<(usize, Option>), ParserError> { + let (consumed, data) = self.parse_item(input, timestamp)?; + Ok((consumed, data.map(LogRecordOutput::Message))) + } +} + #[cfg(test)] mod tests { - use crate::Parser; - use super::*; #[test] fn multiple_parse_calls() { let mut parser = StringTokenizer {}; let content = b"hello\nworld\n"; - let (consumed_1, first_msg) = parser.parse_item(content, None).unwrap(); + let (consumed_1, first_msg) = parser.parse(content, None).unwrap().next().unwrap(); match first_msg { - Some(ParseYield::Message(StringMessage { content })) if content.eq("hello") => {} + Some(LogRecordOutput::Message(content)) if content.eq("hello") => {} _ => panic!("First message did not match"), } let rest_1 = &content[consumed_1..]; println!("rest_1 = {:?}", String::from_utf8_lossy(rest_1)); - let (consumed_2, second_msg) = parser.parse_item(rest_1, None).unwrap(); + let (consumed_2, second_msg) = parser.parse(rest_1, None).unwrap().next().unwrap(); match second_msg { - Some(ParseYield::Message(StringMessage { content })) if content.eq("world") => {} + Some(LogRecordOutput::Message(content)) if content.eq("world") => {} _ => panic!("Second message did not match"), } let rest_2 = &rest_1[consumed_2..]; - let (consumed_3, third_msg) = parser.parse_item(rest_2, None).unwrap(); + let (consumed_3, third_msg) = parser.parse(rest_2, None).unwrap().next().unwrap(); println!( "rest_3 = {:?}", String::from_utf8_lossy(&rest_2[consumed_3..]) @@ -95,12 +86,12 @@ mod tests { let (_consumed_1, first_msg) = items_iter.next().unwrap(); match first_msg { - Some(ParseYield::Message(StringMessage { content })) if content.eq("hello") => {} + Some(LogRecordOutput::Message(content)) if content.eq("hello") => {} _ => panic!("First message did not match"), } let (_consumed_2, second_msg) = items_iter.next().unwrap(); match second_msg { - Some(ParseYield::Message(StringMessage { content })) if content.eq("world") => {} + Some(LogRecordOutput::Message(content)) if content.eq("world") => {} _ => panic!("Second message did not match"), } assert!(items_iter.next().is_none()); diff --git a/application/apps/indexer/plugins_host/Cargo.toml b/application/apps/indexer/plugins_host/Cargo.toml index 8be0604c8..ac38db06e 100644 --- a/application/apps/indexer/plugins_host/Cargo.toml +++ b/application/apps/indexer/plugins_host/Cargo.toml @@ -14,15 +14,18 @@ dirs.workspace = true toml.workspace = true blake3.workspace = true rand.workspace = true - wasmtime = "33.0" wasmtime-wasi = "33.0" - +uuid = { workspace = true , features = ["serde", "v4"] } +# TODO AAZ: Remove when `DescriptorGetter::create` function is async. +tokio-util.workspace = true +descriptor = { path = "../descriptor" } parsers = { path = "../parsers" } sources = { path = "../sources" } stypes = { path = "../stypes" } # TODO : Introduce shared crates and move dir_checksum to it. dir_checksum = { path = "../../../../cli/development-cli/dir_checksum" } +register = { path = "../register" } [dev-dependencies] criterion = { workspace = true, features = ["async_tokio"] } diff --git a/application/apps/indexer/plugins_host/src/parser_shared/mod.rs b/application/apps/indexer/plugins_host/src/parser_shared/mod.rs index a15a43465..6dd7c7f6f 100644 --- a/application/apps/indexer/plugins_host/src/parser_shared/mod.rs +++ b/application/apps/indexer/plugins_host/src/parser_shared/mod.rs @@ -1,10 +1,13 @@ use std::path::{Path, PathBuf}; -use stypes::{PluginInfo, SemanticVersion}; use wasmtime::component::Component; +use descriptor::{CommonDescriptor, ParserDescriptor}; +use parsers::api::*; +use stypes::{PluginInfo, SemanticVersion}; + use crate::{ - PluginHostError, PluginParseMessage, PluginType, WasmPlugin, + PluginHostError, PluginType, WasmPlugin, plugins_shared::{ load::{WasmComponentInfo, load_and_inspect}, plugin_errors::PluginError, @@ -14,9 +17,6 @@ use crate::{ pub mod plugin_parse_message; -/// Marker for a column separator in the output string. -pub const COLUMN_SEP: &str = "\u{0004}"; - /// Uses [`WasmHost`](crate::wasm_host::WasmHost) to communicate with WASM parser plugin. pub struct PluginsParser { /// The actual parser for each supported version in plugins API. @@ -136,47 +136,68 @@ impl PluginErrorLimits { const INCOMPLETE_ERROR_LIMIT: usize = 50; } -use parsers as p; -impl p::Parser for PluginsParser { - fn parse( - &mut self, - input: &[u8], - timestamp: Option, - ) -> Result>)>, p::Error> - { +impl Parser for PluginsParser { + fn parse<'a>(&'a mut self, input: &'a [u8], timestamp: Option) -> ParseReturnIterator<'a> { let res = match &mut self.parser { PlugVerParser::Ver010(parser) => parser.parse(input, timestamp), }; // Check for consecutive errors. match &res { - Ok(_) | Err(p::Error::Unrecoverable(_)) | Err(p::Error::Eof) => { + Ok(_) | Err(ParserError::Unrecoverable(_)) | Err(ParserError::Eof) => { self.errors_counter = 0; } - Err(p::Error::Parse(err)) => { + Err(ParserError::Parse(err)) => { self.errors_counter += 1; if self.errors_counter > PluginErrorLimits::PARSE_ERROR_LIMIT { self.errors_counter = 0; - return Err(p::Error::Unrecoverable(format!( + return Err(ParserError::Unrecoverable(format!( "Plugin parser returned more than \ {} recoverable parse errors consecutively\n. Parse Error: {err}", PluginErrorLimits::PARSE_ERROR_LIMIT ))); } } - Err(p::Error::Incomplete) => { + Err(ParserError::Incomplete) => { self.errors_counter += 1; if self.errors_counter > PluginErrorLimits::INCOMPLETE_ERROR_LIMIT { self.errors_counter = 0; - return Err(p::Error::Unrecoverable(format!( + return Err(ParserError::Unrecoverable(format!( "Plugin parser returned more than \ {} recoverable incomplete errors consecutively", PluginErrorLimits::INCOMPLETE_ERROR_LIMIT ))); } } + Err(ParserError::Native(_err)) => { + todo!("Not implemented") + } } res } } + +#[derive(Default)] +struct Descriptor {} + +impl CommonDescriptor for Descriptor { + fn is_compatible(&self, _origin: &stypes::SessionAction) -> bool { + true + } + /// **ATTANTION** That's placeholder. Should be another way to delivery data + fn ident(&self) -> stypes::Ident { + stypes::Ident { + name: String::from("Plugin"), + desc: String::from("Plugin"), + io: stypes::IODataType::Any, + uuid: uuid::Uuid::new_v4(), + } + } +} + +impl ParserDescriptor for Descriptor { + fn get_render(&self) -> Option { + Some(stypes::OutputRender::PlaitText) + } +} diff --git a/application/apps/indexer/plugins_host/src/parser_shared/plugin_parse_message.rs b/application/apps/indexer/plugins_host/src/parser_shared/plugin_parse_message.rs index e38996db1..a8525d497 100644 --- a/application/apps/indexer/plugins_host/src/parser_shared/plugin_parse_message.rs +++ b/application/apps/indexer/plugins_host/src/parser_shared/plugin_parse_message.rs @@ -1,6 +1,3 @@ -use std::fmt::Display; - -use parsers::LogMessage; use serde::Serialize; /// Represent the message of the parsed item returned by plugins. @@ -9,18 +6,3 @@ pub struct PluginParseMessage { /// The content of the message as string. pub content: String, } - -impl Display for PluginParseMessage { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.content) - } -} - -impl LogMessage for PluginParseMessage { - fn to_writer( - &self, - _writer: &mut W, - ) -> Result { - panic!("Parser plugins don't support export as binary"); - } -} diff --git a/application/apps/indexer/plugins_host/src/plugins_manager/descriptor.rs b/application/apps/indexer/plugins_host/src/plugins_manager/descriptor.rs new file mode 100644 index 000000000..79eac5cd5 --- /dev/null +++ b/application/apps/indexer/plugins_host/src/plugins_manager/descriptor.rs @@ -0,0 +1,208 @@ +use descriptor::{ + CommonDescriptor, ParserDescriptor, ParserFactory, SourceDescriptor, SourceFactory, +}; +use stypes::{ + Field, NativeError, NativeErrorKind, PluginConfigItem, PluginEntity, PluginParserSettings, + SessionAction, Severity, StaticFieldDesc, +}; +use tokio::runtime::Handle; +use tokio_util::sync::CancellationToken; + +use crate::PluginsParser; + +#[derive(Debug)] +pub struct PluginDescriptor { + entity: PluginEntity, +} + +impl PluginDescriptor { + pub fn new(entity: PluginEntity) -> Self { + Self { entity } + } +} + +impl CommonDescriptor for PluginDescriptor { + fn ident(&self) -> stypes::Ident { + let desc = self + .entity + .metadata + .description + .as_ref() + .map_or_else(|| String::from("Parser Plugin"), |desc| desc.to_owned()); + stypes::Ident { + name: self.entity.metadata.title.to_owned(), + desc, + io: stypes::IODataType::Any, + uuid: uuid::Uuid::new_v4(), + } + } + + fn bound_ident( + &self, + _origin: &stypes::SessionAction, + _fields: &[stypes::Field], + ) -> stypes::Ident { + self.ident() + } + + fn fields_getter(&self, _origin: &stypes::SessionAction) -> descriptor::FieldsResult { + let fields: Vec<_> = self + .entity + .info + .config_schemas + .iter() + .map(|conf| { + use stypes::PluginConfigSchemaType as SchType; + use stypes::ValueInput as InType; + let interface = match &conf.input_type { + SchType::Boolean(bool) => InType::Checkbox(*bool), + SchType::Integer(num) => InType::Number(*num as i64), + //TODO AAZ: Clarify why we don't have float numbers. + SchType::Float(num) => InType::Number(*num as i64), + SchType::Text(def_txt) => InType::String(def_txt.to_owned(), String::new()), + SchType::Directories => InType::Directories, + SchType::Files(items) => InType::Files(items.to_vec()), + SchType::Dropdown(items) => { + InType::Strings(items.0.to_owned(), items.1.to_owned()) + } + }; + let static_desc = StaticFieldDesc { + id: conf.id.to_owned(), + name: conf.title.to_owned(), + desc: conf + .description + .as_ref() + .map_or_else(String::new, |d| d.to_owned()), + //TODO AAZ: Map this to plugins API + required: true, + interface, + binding: None, + }; + stypes::FieldDesc::Static(static_desc) + }) + .collect(); + + Ok(fields) + } + + fn lazy_fields_getter( + &self, + _origin: stypes::SessionAction, + _cancel: CancellationToken, + ) -> descriptor::LazyFieldsTask { + Box::pin(async { Ok(Vec::new()) }) + } + + fn validate( + &self, + _origin: &stypes::SessionAction, + _fields: &[stypes::Field], + ) -> Result, stypes::NativeError> { + Ok(std::collections::HashMap::new()) + } + + fn is_compatible(&self, _origin: &stypes::SessionAction) -> bool { + true + } +} + +impl ParserDescriptor for PluginDescriptor { + fn get_render(&self) -> Option { + // TODO AAZ: API on plugins should change to match this changes. + match &self.entity.info.render_options { + stypes::RenderOptions::Parser(parser_render_options) => { + if let Some(opts) = parser_render_options.columns_options.as_ref() { + let cols: Vec<_> = opts + .columns + .iter() + .map(|col| (col.caption.to_owned(), col.width as usize)) + .collect(); + Some(stypes::OutputRender::Columns(cols)) + } else { + Some(stypes::OutputRender::PlaitText) + } + } + stypes::RenderOptions::ByteSource => None, + } + } +} + +impl ParserFactory for PluginDescriptor { + fn create( + &self, + origin: &SessionAction, + options: &[Field], + ) -> Result)>, NativeError> { + let errors = self.validate(origin, options)?; + if !errors.is_empty() { + return Err(NativeError { + kind: NativeErrorKind::Configuration, + severity: Severity::ERROR, + message: Some( + errors + .values() + .map(String::as_str) + .collect::>() + .join("; "), + ), + }); + } + + let configs: Vec = options + .iter() + .map(|opt| { + use stypes::PluginConfigValue as PlVal; + + let val = match opt.value.clone() { + stypes::Value::Boolean(val) => PlVal::Boolean(val), + stypes::Value::Number(num) => PlVal::Integer(num as i32), + stypes::Value::String(txt) => PlVal::Text(txt), + stypes::Value::Directories(path_bufs) => PlVal::Directories(path_bufs), + stypes::Value::Files(path_bufs) => PlVal::Files(path_bufs), + unsupported => panic!("Config {unsupported:?} is unsupported by plugins"), + }; + PluginConfigItem::new(opt.id.to_owned(), val) + }) + .collect(); + + let settings = + PluginParserSettings::new(self.entity.dir_path.to_owned(), Default::default(), configs); + + //TODO AAZ: Temp solution by blocking here. + //Create function should be async + let parse_res = tokio::task::block_in_place(move || { + Handle::current().block_on(async move { + PluginsParser::initialize( + &settings.plugin_path, + &settings.general_settings, + settings.plugin_configs.clone(), + ) + .await + }) + })?; + + let parser = parsers::Parsers::Plugin(Box::new(parse_res)); + + Ok(Some((parser, Some(self.entity.metadata.title.to_owned())))) + } +} + +impl SourceDescriptor for PluginDescriptor { + fn is_sde_supported(&self, _origin: &stypes::SessionAction) -> bool { + false + } +} + +impl SourceFactory for PluginDescriptor { + fn create( + &self, + _origin: &stypes::SessionAction, + _options: &[stypes::Field], + ) -> Result)>, stypes::NativeError> { + Err(NativeError { + severity: Severity::WARNING, + kind: NativeErrorKind::NotYetImplemented, + message: Some("Support for sources isn't implemented yet".into()), + }) + } +} diff --git a/application/apps/indexer/plugins_host/src/plugins_manager/mod.rs b/application/apps/indexer/plugins_host/src/plugins_manager/mod.rs index 86808cb24..b385bea7e 100644 --- a/application/apps/indexer/plugins_host/src/plugins_manager/mod.rs +++ b/application/apps/indexer/plugins_host/src/plugins_manager/mod.rs @@ -2,6 +2,7 @@ //! providing plugins state, infos and metadata. mod cache; +mod descriptor; mod errors; mod load; pub mod paths; @@ -13,8 +14,10 @@ use std::path::{Path, PathBuf}; use crate::plugins_shared::load::{WasmComponentInfo, load_and_inspect}; use cache::CacheManager; +use descriptor::PluginDescriptor; use load::{PluginEntityState, load_plugin}; use paths::extract_plugin_file_paths; +use register::Register; use stypes::{ ExtendedInvalidPluginEntity, ExtendedPluginEntity, InvalidPluginEntity, PluginEntity, PluginLogLevel, PluginRunData, PluginType, @@ -314,6 +317,22 @@ impl PluginsManager { Ok(()) } + + pub fn register_plugins(&self, components: &mut Register) -> Result<(), stypes::NativeError> { + for plugin in &self.installed_plugins { + let plug_info = plugin.entity.clone(); + match plugin.entity.plugin_type { + PluginType::Parser => { + components.add_parser(PluginDescriptor::new(plug_info))?; + } + PluginType::ByteSource => { + components.add_source(PluginDescriptor::new(plug_info))?; + } + } + } + + Ok(()) + } } /// Provides the root path of the plugins directory for the provided plugin type. diff --git a/application/apps/indexer/plugins_host/src/v0_1_0/parser/bindings.rs b/application/apps/indexer/plugins_host/src/v0_1_0/parser/bindings.rs index b6089d892..d836de564 100644 --- a/application/apps/indexer/plugins_host/src/v0_1_0/parser/bindings.rs +++ b/application/apps/indexer/plugins_host/src/v0_1_0/parser/bindings.rs @@ -1,9 +1,9 @@ -use crate::{PluginParseMessage, parser_shared::COLUMN_SEP}; +use parsers::api; +use std::borrow::Cow; +use stypes::ParserRenderOptions; pub use self::chipmunk::parser::parse_types::*; -use stypes::ParserRenderOptions; - wasmtime::component::bindgen!({ path: "../../../../plugins/plugins_api/wit/v0.1.0", world: "chipmunk:parser/parse", @@ -41,23 +41,26 @@ impl From<&stypes::PluginParserGeneralSettings> for ParserConfig { } } -use parsers as p; - -impl From for p::ParseYield { - fn from(yld: ParseYield) -> Self { - match yld { - ParseYield::Message(msg) => p::ParseYield::Message(msg.into()), - ParseYield::Attachment(att) => p::ParseYield::Attachment(att.into()), - ParseYield::MessageAndAttachment((msg, att)) => { - p::ParseYield::MessageAndAttachment((msg.into(), att.into())) +impl From for api::LogRecordOutput<'_> { + fn from(value: ParseYield) -> Self { + match value { + ParseYield::Message(parsed_message) => parsed_message.into(), + ParseYield::Attachment(attachment) => { + api::LogRecordOutput::Attachment(attachment.into()) + } + ParseYield::MessageAndAttachment((msg, attachment)) => { + api::LogRecordOutput::Multiple(vec![ + msg.into(), + api::LogRecordOutput::Attachment(attachment.into()), + ]) } } } } -impl From for p::Attachment { +impl From for api::Attachment { fn from(att: Attachment) -> Self { - p::Attachment { + api::Attachment { data: att.data, name: att.name, size: att.size as usize, @@ -68,25 +71,25 @@ impl From for p::Attachment { } } -impl From for p::Error { +impl From for api::ParserError { fn from(err: ParseError) -> Self { match err { - ParseError::Unrecoverable(msg) => p::Error::Unrecoverable(msg), - ParseError::Parse(msg) => p::Error::Parse(msg), - ParseError::Incomplete => p::Error::Incomplete, - ParseError::Eof => p::Error::Eof, + ParseError::Unrecoverable(msg) => api::ParserError::Unrecoverable(msg), + ParseError::Parse(msg) => api::ParserError::Parse(msg), + ParseError::Incomplete => api::ParserError::Incomplete, + ParseError::Eof => api::ParserError::Eof, } } } -impl From for PluginParseMessage { +impl From for api::LogRecordOutput<'_> { fn from(msg: ParsedMessage) -> Self { - let content = match msg { - ParsedMessage::Line(msg) => msg, - ParsedMessage::Columns(columns) => columns.join(COLUMN_SEP), - }; - - Self { content } + match msg { + ParsedMessage::Line(msg) => api::LogRecordOutput::Message(msg.into()), + ParsedMessage::Columns(columns) => { + api::LogRecordOutput::Columns(columns.into_iter().map(Cow::Owned).collect()) + } + } } } diff --git a/application/apps/indexer/plugins_host/src/v0_1_0/parser/mod.rs b/application/apps/indexer/plugins_host/src/v0_1_0/parser/mod.rs index 8c2f46507..7bc5099a1 100644 --- a/application/apps/indexer/plugins_host/src/v0_1_0/parser/mod.rs +++ b/application/apps/indexer/plugins_host/src/v0_1_0/parser/mod.rs @@ -4,6 +4,8 @@ mod bindings; mod parser_plugin_state; +use descriptor::{CommonDescriptor, ParserDescriptor}; +use parsers::api::*; use stypes::{ParserRenderOptions, RenderOptions, SemanticVersion}; use tokio::runtime::Handle; use wasmtime::{ @@ -13,7 +15,7 @@ use wasmtime::{ use wasmtime_wasi::{ResourceTable, p2::WasiCtx}; use crate::{ - PluginGuestError, PluginHostError, PluginParseMessage, + PluginGuestError, PluginHostError, plugins_shared::{PluginInfo, get_wasi_ctx_builder, plugin_errors::PluginError}, wasm_host::get_wasm_host, }; @@ -23,6 +25,7 @@ use self::{bindings::Parse, parser_plugin_state::ParserPluginState}; /// Host of the parser plugin for plugins API version 0.1.0 pub struct PluginParser { store: Store, + plugin_bindings: Parse, } @@ -130,14 +133,8 @@ impl PluginParser { } } -use parsers as p; -impl p::Parser for PluginParser { - fn parse( - &mut self, - input: &[u8], - timestamp: Option, - ) -> Result>)>, p::Error> - { +impl Parser for PluginParser { + fn parse<'a>(&mut self, input: &'a [u8], timestamp: Option) -> ParseReturnIterator<'a> { // Calls on plugins must be async. To solve that we got the following solutions: // - `futures::executor::block_on(plugin_call)`: Blocks the current Tokio worker with a local // executor. Risks are with blocking the whole runtime as Tokio isn't notified. @@ -157,15 +154,43 @@ impl p::Parser for PluginParser { Ok(results) => results?, Err(call_err) => { // Wasmtime uses anyhow error, which provides error context in debug print only. - return Err(p::Error::Unrecoverable(format!( + return Err(ParserError::Unrecoverable(format!( "Call parse on the plugin failed. Error: {call_err:?}" ))); } }; - let res = parse_results + let items = parse_results .into_iter() .map(|item| (item.consumed as usize, item.value.map(|v| v.into()))); - Ok(res) + + let iter = Box::new(items) + as Box<(dyn Iterator>)> + 'a)>; + + Ok(iter) + } +} + +#[derive(Default)] +struct Descriptor {} + +impl CommonDescriptor for Descriptor { + fn is_compatible(&self, _origin: &stypes::SessionAction) -> bool { + true + } + /// **ATTANTION** That's placeholder. Should be another way to delivery data + fn ident(&self) -> stypes::Ident { + stypes::Ident { + name: String::from("Plugin Parser"), + desc: String::from("Plugin Parser"), + io: stypes::IODataType::Any, + uuid: uuid::Uuid::new_v4(), + } + } +} + +impl ParserDescriptor for Descriptor { + fn get_render(&self) -> Option { + Some(stypes::OutputRender::PlaitText) } } diff --git a/application/apps/indexer/processor/Cargo.toml b/application/apps/indexer/processor/Cargo.toml index 2601c3aa4..fb774a3d7 100644 --- a/application/apps/indexer/processor/Cargo.toml +++ b/application/apps/indexer/processor/Cargo.toml @@ -6,25 +6,27 @@ edition = "2024" [dependencies] bincode = "1.3" -bufread = { path = "../addons/bufread" } bytecount = "0.6" futures.workspace = true grep-regex.workspace = true grep-searcher.workspace = true -indexer_base = { path = "../indexer_base" } itertools = "0.13" lazy_static.workspace = true log.workspace = true -parsers = { path = "../parsers" } -sources = { path = "../sources" } regex.workspace = true serde = { workspace = true , features = ["derive"] } serde_json.workspace = true -text_grep = { path = "../addons/text_grep" } thiserror.workspace = true tokio-util.workspace = true +tokio = { version = "1", features = ["full"] } +tokio-stream = "0.1" uuid = { workspace = true , features = ["serde", "v4"] } +bufread = { path = "../addons/bufread" } +text_grep = { path = "../addons/text_grep" } stypes = { path = "../stypes", features=["rustcore"] } +parsers = { path = "../parsers" } +sources = { path = "../sources" } +indexer_base = { path = "../indexer_base" } [dev-dependencies] criterion.workspace = true diff --git a/application/apps/indexer/processor/src/export/mod.rs b/application/apps/indexer/processor/src/export/mod.rs deleted file mode 100644 index 6d5b13c22..000000000 --- a/application/apps/indexer/processor/src/export/mod.rs +++ /dev/null @@ -1,238 +0,0 @@ -use std::{ - io::{BufWriter, Write}, - path::Path, -}; - -use indexer_base::config::IndexSection; -use parsers::{LogMessage, MessageStreamItem, ParseYield, Parser}; -use sources::{ByteSource, producer::MessageProducer}; -use thiserror::Error; -use tokio_util::sync::CancellationToken; - -#[derive(Error, Debug)] -pub enum ExportError { - #[error("Configuration error ({0})")] - Config(String), - #[error("IO error: {0:?}")] - Io(#[from] std::io::Error), - #[error("Cancelled")] - Cancelled, -} - -/// Exporting data as raw into a given destination. Would be exported only data -/// defined in selections as indexes of rows (messages). -/// -/// Returns usize - count of read messages (not exported, but read messages) -/// -/// # Arguments -/// * `s` - instance of MessageProducer stream -/// * `destination_path` - destination path to a target file. If the file doesn't -/// exist it would be created; if exists - it will be opened to append new -/// content to the end of the file -/// * `sections` - an array of ranges, which should be written into destination -/// file -/// * `read_to_end` - in "true" will continue iterating stream after all ranges -/// are processed; in "false" will stop listening to a stream as soon as all ranges -/// are processed. It should be used in "true" for example if exporting applied -/// to concatenated files. -/// * `cancel` - cancellation token to stop operation -/// -/// # Errors -/// In case of cancellation will return ExportError::Cancelled -pub async fn export_raw( - mut producer: MessageProducer, - destination_path: &Path, - sections: &Vec, - read_to_end: bool, - text_file: bool, - cancel: &CancellationToken, -) -> Result -where - T: LogMessage + Sized, - P: Parser, - D: ByteSource, -{ - trace!("export_raw, sections: {sections:?}"); - if !sections_valid(sections) { - return Err(ExportError::Config("Invalid sections".to_string())); - } - let out_file = if destination_path.exists() { - std::fs::OpenOptions::new() - .append(true) - .open(destination_path)? - } else { - std::fs::File::create(destination_path)? - }; - let mut out_writer = BufWriter::new(out_file); - let mut section_index = 0usize; - let mut current_index = 0usize; - let mut inside = false; - let mut exported = 0usize; - if sections.is_empty() { - debug!("no sections configured"); - // export everything - 'outer: while let Some(items) = producer.read_next_segment().await { - if cancel.is_cancelled() { - return Err(ExportError::Cancelled); - } - - for (_, item) in items { - let written = match item { - MessageStreamItem::Item(ParseYield::Message(msg)) => { - msg.to_writer(&mut out_writer)?; - true - } - MessageStreamItem::Item(ParseYield::MessageAndAttachment((msg, _))) => { - msg.to_writer(&mut out_writer)?; - true - } - MessageStreamItem::Done => break 'outer, - _ => false, - }; - if written && text_file { - out_writer.write_all("\n".as_bytes())?; - } - if written { - exported += 1; - } - } - } - return Ok(exported); - } - - 'outer: while let Some(items) = producer.read_next_segment().await { - if cancel.is_cancelled() { - return Err(ExportError::Cancelled); - } - for (_, item) in items { - if !inside { - if sections[section_index].first_line == current_index { - inside = true; - } - } else if sections[section_index].last_line < current_index { - inside = false; - section_index += 1; - if sections.len() <= section_index { - // no more sections - if matches!(item, MessageStreamItem::Item(_)) { - current_index += 1; - } - break 'outer; - } - // check if we are in next section again - if sections[section_index].first_line == current_index { - inside = true; - } - } - let written = match item { - MessageStreamItem::Item(ParseYield::Message(msg)) => { - if inside { - msg.to_writer(&mut out_writer)?; - } - current_index += 1; - inside - } - MessageStreamItem::Item(ParseYield::MessageAndAttachment((msg, _))) => { - if inside { - msg.to_writer(&mut out_writer)?; - } - current_index += 1; - inside - } - MessageStreamItem::Done => { - debug!("No more messages to export"); - break 'outer; - } - _ => false, - }; - if written && text_file { - out_writer.write_all("\n".as_bytes())?; - } - } - } - if read_to_end { - 'outer: while let Some(items) = producer.read_next_segment().await { - if cancel.is_cancelled() { - return Err(ExportError::Cancelled); - } - for (_, item) in items { - match item { - MessageStreamItem::Item(_) => { - current_index += 1; - } - MessageStreamItem::Done => { - break 'outer; - } - _ => {} - } - } - } - } - debug!("export_raw done ({current_index} messages)"); - Ok(current_index) -} - -fn sections_valid(sections: &[IndexSection]) -> bool { - let pairs = sections.iter().zip(sections.iter().skip(1)); - for p in pairs { - if p.0.last_line >= p.1.first_line { - // overlap - return false; - } - } - sections.iter().all(|s| s.first_line <= s.last_line) -} - -#[test] -fn test_sections_valid_valid_single_section() { - let sections = vec![IndexSection { - first_line: 0, - last_line: 2, - }]; - assert!(sections_valid(§ions)); -} -#[test] -fn test_sections_valid_validity_valid_sections_no_gap() { - let sections = vec![ - IndexSection { - first_line: 0, - last_line: 2, - }, - IndexSection { - first_line: 3, - last_line: 4, - }, - ]; - assert!(sections_valid(§ions)); -} -#[test] -fn test_sections_valid_invalidity_valid_single_section() { - let sections = vec![IndexSection { - first_line: 2, - last_line: 1, - }]; - assert!(!sections_valid(§ions)); -} -#[test] -fn test_sections_valid_empty_sections() { - let sections = vec![]; - assert!(sections_valid(§ions)); -} -#[test] -fn test_sections_valid_invalidity_overlapping() { - let sections = vec![ - IndexSection { - first_line: 1, - last_line: 4, - }, - IndexSection { - first_line: 9, - last_line: 11, - }, - IndexSection { - first_line: 11, - last_line: 15, - }, - ]; - assert!(!sections_valid(§ions)); -} diff --git a/application/apps/indexer/processor/src/lib.rs b/application/apps/indexer/processor/src/lib.rs index 7c9e561b5..5c1080457 100644 --- a/application/apps/indexer/processor/src/lib.rs +++ b/application/apps/indexer/processor/src/lib.rs @@ -15,10 +15,10 @@ extern crate lazy_static; #[macro_use] extern crate log; -pub mod export; pub mod grabber; pub mod map; pub mod processor; +pub mod producer; pub mod search; pub mod text_source; diff --git a/application/apps/indexer/sources/src/producer.rs b/application/apps/indexer/processor/src/producer/mod.rs similarity index 72% rename from application/apps/indexer/sources/src/producer.rs rename to application/apps/indexer/processor/src/producer/mod.rs index 2504c4574..4d88088a2 100644 --- a/application/apps/indexer/sources/src/producer.rs +++ b/application/apps/indexer/processor/src/producer/mod.rs @@ -1,45 +1,68 @@ -#[cfg(test)] -mod tests; - -use crate::{ByteSource, ReloadInfo, SourceFilter}; +pub mod sde; use log::warn; -use parsers::{Error as ParserError, LogMessage, MessageStreamItem, Parser}; -use std::marker::PhantomData; +use parsers::api::*; +use sources::api::*; + +/// ********************************************************************************************** +/// For Ammar: +/// +/// It makes sense to revisit the design of MessageProducer with the following considerations: +/// Remove the inner loop, which no longer appears necessary after all recent refactorings. +/// This would allow read_next_segment to simply read an available data fragment without +/// performing iteration, which should be handled at a higher level. +/// +/// Introduce an enum ProducerAction to group the logic by intent. Currently, in several +/// error-handling branches, we effectively duplicate code. Representing the different "actions" +/// explicitly would help make the logic inside read_next_segment clearer. For example: +/// +/// enum ProducerAction { +/// Return(..), // Return results +/// DropAndLoad(), // Attempt to load more data +/// ... +/// } +/// ********************************************************************************************** /// Number of bytes to skip on initial parse errors before terminating the session. const INITIAL_PARSE_ERROR_LIMIT: usize = 1024; #[derive(Debug)] -pub struct MessageProducer +pub enum MessageStreamItem { + Parsed(ParseOperationResult), + Skipped, + Done, +} + +#[derive(Debug)] +pub struct MessageProducer<'a, P, D, B> where - T: LogMessage, - P: Parser, + P: Parser, D: ByteSource, + B: LogRecordsBuffer, { byte_source: D, parser: P, filter: Option, last_seen_ts: Option, - _phantom_data: Option>, total_loaded: usize, total_skipped: usize, done: bool, - buffer: Vec<(usize, MessageStreamItem)>, + logs_buffer: &'a mut B, + total_produced_items: usize, } -impl, D: ByteSource> MessageProducer { +impl<'a, P: Parser, D: ByteSource, B: LogRecordsBuffer> MessageProducer<'a, P, D, B> { /// create a new producer by plugging into a byte source - pub fn new(parser: P, source: D) -> Self { + pub fn new(parser: P, source: D, logs_buffer: &'a mut B) -> Self { MessageProducer { byte_source: source, parser, filter: None, last_seen_ts: None, - _phantom_data: None, total_loaded: 0, total_skipped: 0, done: false, - buffer: Vec::new(), + logs_buffer, + total_produced_items: 0, } } @@ -53,7 +76,7 @@ impl, D: ByteSource> MessageProducer { /// # Return: /// Return a mutable reference for the newly parsed items when there are more data available, /// otherwise it returns None when there are no more data available in the source. - pub async fn read_next_segment(&mut self) -> Option<&mut Vec<(usize, MessageStreamItem)>> { + pub async fn read_next_segment(&mut self) -> Option<(usize, MessageStreamItem)> { // ### Cancel Safety ###: // This function is cancel safe because: // * there is no await calls or any function causing yielding between filling the internal @@ -61,8 +84,6 @@ impl, D: ByteSource> MessageProducer { // * Byte source will keep the loaded data in its internal buffer ensuring there // is no data loss when cancelling happen between load calls. - self.buffer.clear(); - if self.done { debug!("done...no next segment"); return None; @@ -94,49 +115,54 @@ impl, D: ByteSource> MessageProducer { if available == 0 { trace!("No more bytes available from source"); self.done = true; - self.buffer.push((0, MessageStreamItem::Done)); - return Some(&mut self.buffer); + if let Err(err) = self.logs_buffer.flush().await { + error!("Fail to write data into writer: {err}"); + } + return Some((0, MessageStreamItem::Done)); } - // we can call consume only after all parse results are collected because of its // reference to self. let mut total_consumed = 0; + let mut messages_received = 0; match self .parser - .parse(self.byte_source.current_slice(), self.last_seen_ts) + .parse(current_slice, self.last_seen_ts) .map(|iter| { - iter.for_each(|item| match item { + iter.for_each(|item| {match item { (consumed, Some(m)) => { let total_used_bytes = consumed + skipped_bytes; // Reset skipped bytes since it had been counted here. skipped_bytes = 0; debug!( - "Extracted a valid message, consumed {consumed} bytes (total used {total_used_bytes} bytes)" + "Extracted a valid message, consumed {consumed} bytes (total used {total_used_bytes} bytes)" ); total_consumed += consumed; - self.buffer - .push((total_used_bytes, MessageStreamItem::Item(m))); + messages_received += 1; + + self.logs_buffer.append(m) } (consumed, None) => { total_consumed += consumed; trace!("None, consumed {consumed} bytes"); - let total_used_bytes = consumed + skipped_bytes; - // Reset skipped bytes since it had been counted here. - skipped_bytes = 0; - self.buffer - .push((total_used_bytes, MessageStreamItem::Skipped)); } - }) + }}) }) { Ok(()) => { - // Ensure `did_produce_items()` correctness over time. - if cfg!(debug_assertions) && !self.buffer.is_empty() { - assert!(self.did_produce_items()); + self.total_produced_items += messages_received; + if messages_received > 0 { + if let Err(err) = self.logs_buffer.flush().await { + error!("Fail to write data into writer: {err}"); + } } - self.byte_source.consume(total_consumed); - return Some(&mut self.buffer); + return Some(( + total_consumed, + MessageStreamItem::Parsed(ParseOperationResult::new( + total_consumed, + messages_received, + )), + )); } Err(ParserError::Incomplete) => { trace!("not enough bytes to parse a message. Load more data"); @@ -155,16 +181,16 @@ impl, D: ByteSource> MessageProducer { ); let unused = skipped_bytes + available; self.done = true; - - self.buffer.push((unused, MessageStreamItem::Done)); - return Some(&mut self.buffer); + if let Err(err) = self.logs_buffer.flush().await { + error!("Fail to write data into writer: {err}"); + } + return Some((unused, MessageStreamItem::Done)); } } } Err(ParserError::Eof) => { - trace!("EOF reached...no more messages (skipped_bytes={skipped_bytes})"); + trace!("EOF reached...no more messages (skipped_bytes={skipped_bytes})",); self.done = true; - return None; } Err(ParserError::Parse(s)) => { @@ -182,9 +208,10 @@ impl, D: ByteSource> MessageProducer { ); let unused = skipped_bytes + available; self.done = true; - - self.buffer.push((unused, MessageStreamItem::Done)); - return Some(&mut self.buffer); + if let Err(err) = self.logs_buffer.flush().await { + error!("Fail to write data into writer: {err}"); + } + return Some((unused, MessageStreamItem::Done)); } trace!("No parse possible, skip one byte and retry. Error: {s}"); @@ -196,9 +223,10 @@ impl, D: ByteSource> MessageProducer { ); let unused = skipped_bytes + available; self.done = true; - - self.buffer.push((unused, MessageStreamItem::Done)); - return Some(&mut self.buffer); + if let Err(err) = self.logs_buffer.flush().await { + error!("Fail to write data into writer: {err}"); + } + return Some((unused, MessageStreamItem::Done)); } } Err(ParserError::Unrecoverable(err)) => { @@ -208,9 +236,13 @@ impl, D: ByteSource> MessageProducer { error!("Parsing failed: Error {err}"); eprintln!("Parsing failed: Error: {err}"); self.done = true; - self.buffer.push((0, MessageStreamItem::Done)); - - return Some(&mut self.buffer); + if let Err(err) = self.logs_buffer.flush().await { + error!("Fail to write data into writer: {err}"); + } + return Some((0, MessageStreamItem::Done)); + } + Err(ParserError::Native(_err)) => { + todo!("Not Implemented") } } } @@ -302,17 +334,14 @@ impl, D: ByteSource> MessageProducer { /// Checks if the producer have already produced any parsed items in the current session. fn did_produce_items(&self) -> bool { - // Produced items will be added to the internal buffer increasing its capacity. - // This isn't straight forward way, but used to avoid having to introduce a new field - // and keep track on its state. - self.buffer.capacity() > 0 + self.total_produced_items > 0 } /// Append incoming (SDE) Source-Data-Exchange to the underline byte source data. pub async fn sde_income( &mut self, msg: stypes::SdeRequest, - ) -> Result { + ) -> Result { self.byte_source.income(msg).await } } diff --git a/application/apps/indexer/sources/src/producer/tests/cancel_safety.rs b/application/apps/indexer/processor/src/producer/producer/tests/cancel_safety.rs similarity index 100% rename from application/apps/indexer/sources/src/producer/tests/cancel_safety.rs rename to application/apps/indexer/processor/src/producer/producer/tests/cancel_safety.rs diff --git a/application/apps/indexer/sources/src/producer/tests/mock_byte_source.rs b/application/apps/indexer/processor/src/producer/producer/tests/mock_byte_source.rs similarity index 100% rename from application/apps/indexer/sources/src/producer/tests/mock_byte_source.rs rename to application/apps/indexer/processor/src/producer/producer/tests/mock_byte_source.rs diff --git a/application/apps/indexer/sources/src/producer/tests/mock_parser.rs b/application/apps/indexer/processor/src/producer/producer/tests/mock_parser.rs similarity index 100% rename from application/apps/indexer/sources/src/producer/tests/mock_parser.rs rename to application/apps/indexer/processor/src/producer/producer/tests/mock_parser.rs diff --git a/application/apps/indexer/sources/src/producer/tests/mod.rs b/application/apps/indexer/processor/src/producer/producer/tests/mod.rs similarity index 100% rename from application/apps/indexer/sources/src/producer/tests/mod.rs rename to application/apps/indexer/processor/src/producer/producer/tests/mod.rs diff --git a/application/apps/indexer/sources/src/producer/tests/multi_parse.rs b/application/apps/indexer/processor/src/producer/producer/tests/multi_parse.rs similarity index 100% rename from application/apps/indexer/sources/src/producer/tests/multi_parse.rs rename to application/apps/indexer/processor/src/producer/producer/tests/multi_parse.rs diff --git a/application/apps/indexer/sources/src/producer/tests/single_parse.rs b/application/apps/indexer/processor/src/producer/producer/tests/single_parse.rs similarity index 100% rename from application/apps/indexer/sources/src/producer/tests/single_parse.rs rename to application/apps/indexer/processor/src/producer/producer/tests/single_parse.rs diff --git a/application/apps/indexer/sources/src/sde.rs b/application/apps/indexer/processor/src/producer/sde.rs similarity index 100% rename from application/apps/indexer/sources/src/sde.rs rename to application/apps/indexer/processor/src/producer/sde.rs diff --git a/application/apps/indexer/register/Cargo.toml b/application/apps/indexer/register/Cargo.toml new file mode 100644 index 000000000..f8b117862 --- /dev/null +++ b/application/apps/indexer/register/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "register" +version = "0.1.0" +edition = "2021" + +[dependencies] +thiserror.workspace = true +uuid = { workspace = true , features = ["serde", "v4"] } +tokio-util.workspace = true +tokio = { workspace = true , features = ["full"] } +log.workspace = true +indexmap.workspace = true + +stypes = { path = "../stypes", features=["rustcore"] } +descriptor = { path = "../descriptor" } +parsers = { path = "../parsers" } +sources = { path = "../sources" } +file-tools = { path = "../addons/file-tools" } \ No newline at end of file diff --git a/application/apps/indexer/register/src/lib.rs b/application/apps/indexer/register/src/lib.rs new file mode 100644 index 000000000..6c3571d9c --- /dev/null +++ b/application/apps/indexer/register/src/lib.rs @@ -0,0 +1,587 @@ +use std::{collections::HashMap, fmt}; + +use file_tools::is_path_binary; +use indexmap::IndexMap; +use stypes::{NativeError, SessionDescriptor}; +use tokio_util::sync::CancellationToken; +use uuid::Uuid; + +use descriptor::*; +use parsers::Parsers; +use sources::Sources; + +/// The `Register` struct holds information about all available source and parser components +/// in the system. It is used as a central registry during initialization and component creation. +/// +/// Each entry associates a unique identifier (`Uuid`) with the corresponding factory object. +/// These factories are responsible for creating concrete instances of sources and parsers +/// and also act as their descriptors by implementing the associated traits. +/// +/// - `sources`: A map of source factories. Each factory implements `SourceFactory` +/// and provides metadata and construction logic for a specific source type. +/// - `parsers`: A map of parser factories. Each factory implements `ParserFactory` +/// and defines both the parser metadata and instantiation logic. +/// +/// By storing trait objects (`Box`), the registry supports dynamic extensibility, +/// including plugin-based components. +pub struct Register { + sources: IndexMap>>, + parsers: IndexMap>>, +} + +impl Register { + /// Creates an empty `Register` with no registered parsers or sources. + /// + /// This is typically used at system startup before components are added via + /// [`add_parser`] or [`add_source`]. + pub fn new() -> Register { + Self { + sources: IndexMap::new(), + parsers: IndexMap::new(), + } + } + + /// Registers a new parser factory in the registry. + /// + /// The factory must implement the `ParserFactory` trait and will be stored + /// under its associated UUID. If a parser with the same UUID has already been registered, + /// an error of kind `Configuration` will be returned. + /// + /// # Type Parameters + /// - `D`: A concrete type that implements `ParserFactory`. + /// + /// # Errors + /// Returns `NativeError` if a parser with the same UUID is already registered. + pub fn add_parser + 'static>( + &mut self, + factory: D, + ) -> Result<(), stypes::NativeError> { + let ident = factory.ident(); + if self.parsers.contains_key(&ident.uuid) { + return Err(stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Configuration, + message: Some(format!("{} ({}) already registred", ident.name, ident.uuid)), + }); + } + self.parsers.insert(ident.uuid, Box::new(factory)); + Ok(()) + } + + /// Registers a new source factory in the registry. + /// + /// The factory must implement the `SourceFactory` trait and will be stored + /// under its associated UUID. If a source with the same UUID has already been registered, + /// an error of kind `Configuration` will be returned. + /// + /// # Type Parameters + /// - `D`: A concrete type that implements `SourceFactory`. + /// + /// # Errors + /// Returns `NativeError` if a source with the same UUID is already registered. + pub fn add_source + 'static>( + &mut self, + factory: D, + ) -> Result<(), stypes::NativeError> { + let ident: stypes::Ident = factory.ident(); + if self.sources.contains_key(&ident.uuid) { + return Err(stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Configuration, + message: Some(format!("{} ({}) already registred", ident.name, ident.uuid)), + }); + } + self.sources.insert(ident.uuid, Box::new(factory)); + Ok(()) + } + + /// Returns a list of parser identifiers compatible with the given session context. + /// + /// Filters registered parsers using their `is_compatible` logic and returns + /// only those that can be used for the given session action. + /// + /// # Arguments + /// * `origin` – The session action context to filter compatible parsers. + /// + /// # Returns + /// A list of identifiers (`Ident`) describing the matching parser components. + pub fn get_parsers(&self, origin: stypes::SessionAction) -> Vec { + self.parsers + .iter() + .filter_map(|(_, entity)| { + if entity.is_compatible(&origin) { + Some(entity.ident()) + } else { + None + } + }) + .collect() + } + + /// Returns a list of source identifiers compatible with the given session context. + /// + /// Filters registered sources using their `is_compatible` logic and returns + /// only those that can be used for the given session action. + /// + /// # Arguments + /// * `origin` – The session action context to filter compatible sources. + /// + /// # Returns + /// A list of identifiers (`Ident`) describing the matching source components. + pub fn get_sources(&self, origin: stypes::SessionAction) -> Vec { + self.sources + .iter() + .filter_map(|(_, entity)| { + if entity.is_compatible(&origin) { + Some(entity.ident()) + } else { + None + } + }) + .collect() + } + + /// Returns the identifiers of all registered components of a given type. + /// + /// This method retrieves the list of identifiers (`Ident`) for all components + /// that match the specified [`ComponentType`], such as parsers or sources. The + /// results can be filtered based on the provided [`SessionAction`] origin, + /// allowing context-specific queries. + /// + /// # Arguments + /// + /// * `ty` - The type of component to retrieve (e.g., `Parser` or `Source`). + /// * `origin` - The session origin used to filter components by their scope or context. + /// + /// # Returns + /// + /// A `Vec` containing the identifiers of all matching components. + /// + /// # See Also + /// + /// [`get_parsers`], [`get_sources`] + pub fn get_components( + &self, + ty: stypes::ComponentType, + origin: stypes::SessionAction, + ) -> Vec { + match ty { + stypes::ComponentType::Parser => self.get_parsers(origin), + stypes::ComponentType::Source => self.get_sources(origin), + } + } + + /// Returns configuration schemas for the specified component UUIDs. + /// + /// For each component (parser or source), this method returns the `OptionsScheme` used to render configuration UI + /// and optionally define lazy-loaded settings. + /// + /// # Arguments + /// * `origin` – Session action describing the context in which components will be used. + /// * `targets` – A list of component UUIDs to fetch configuration for. + /// + /// # Returns + /// * `Ok(HashMap)` – Mapping from component UUIDs to their configuration schemes. + /// * `Err(NativeError)` – If any of the UUIDs are not found in the registry. + /// + /// # Errors + /// Returns a configuration error with a list of UUIDs that were not found in the registry. + pub fn get_options( + &self, + origin: stypes::SessionAction, + mut targets: Vec, + ) -> Result, stypes::NativeError> { + let descriptors = self + .descriptors() + .into_iter() + .filter_map(|(uuid, entity)| { + if targets.contains(uuid) { + targets.retain(|v| v != uuid); + Some(entity) + } else { + None + } + }) + .collect::>(); + if !targets.is_empty() { + return Err(stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Configuration, + message: Some(format!( + "Fail to find: {}", + targets + .iter() + .map(|v| v.to_string()) + .collect::>() + .join(", ") + )), + }); + } + let mut list: HashMap = HashMap::new(); + for entity in descriptors.into_iter() { + let mut options = OptionsScheme::new(entity.fields_getter(&origin)?); + if options.has_lazy() { + let cancel = CancellationToken::new(); + options.set_lazy( + entity.ident(), + entity.lazy_fields_getter(origin.clone(), cancel.clone()), + &cancel, + ); + } + list.insert(entity.ident().uuid, options); + } + Ok(list) + } + + /// Returns the client-side render type for the specified component UUID. + /// + /// While typically associated with parsers (since they define the data format), + /// the system allows any component to provide rendering output hints. + /// + /// # Arguments + /// * `uuid` – The UUID of the target component. + /// + /// # Returns + /// * `Ok(Some(OutputRender))` – If the parser provides render information. + /// * `Ok(None)` – If no render is defined. + /// * `Err(NativeError)` – If the UUID is not found or the component is not a parser. + /// + /// # Errors + /// Returns a configuration error if the component is missing or not a parser. + pub fn get_output_render( + &self, + uuid: &Uuid, + ) -> Result, stypes::NativeError> { + let descriptor = self.parsers.get(uuid).ok_or(stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Configuration, + message: Some(format!("Fail to find component {uuid}")), + })?; + Ok(descriptor.get_render()) + } + + /// Checks whether the specified source supports the Source Data Exchange (SDE) mechanism. + /// + /// The SDE mechanism determines whether the user is allowed to send data to the source. + /// This method queries the source descriptor by its UUID and evaluates SDE support + /// in the context of the given session origin. + /// + /// # Arguments + /// + /// * `uuid` - The unique identifier of the source component. + /// * `origin` - The session context used to evaluate access permissions. + /// + /// # Returns + /// + /// `Ok(true)` if the source supports SDE; `Ok(false)` otherwise. + /// Returns an error if the source with the specified UUID is not found. + /// + /// # Errors + /// + /// Returns [`NativeError`] with kind [`Configuration`] if the source is not registered. + pub fn is_sde_supported( + &self, + uuid: &Uuid, + origin: &stypes::SessionAction, + ) -> Result { + let descriptor = self.sources.get(uuid).ok_or(stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Configuration, + message: Some(format!("Fail to find component {uuid}")), + })?; + Ok(descriptor.is_sde_supported(origin)) + } + + /// Returns the Ident of component (parser or source). + /// + /// # Arguments + /// * `uuid` – The UUID of the target component. + /// + /// # Returns + /// * `Some(Ident)` – If the parser or source has been found. + /// * `None` – If no component is defined. + pub fn get_ident(&self, uuid: &Uuid) -> Option { + self.descriptors().into_iter().find_map(|(inner, entity)| { + if uuid == inner { + Some(entity.ident()) + } else { + None + } + }) + } + + /// Validates the configuration fields for a specified component within a session context. + /// + /// Checks each field against the component’s declared rules. This helps catch invalid or incomplete + /// configurations before actual component instantiation. + /// + /// # Arguments + /// * `origin` – The session action context. + /// * `target` – The UUID of the component to validate. + /// * `fields` – A list of fields provided by the client. + /// + /// # Returns + /// * `Ok(HashMap)` – Empty if validation passed; otherwise, maps field UUIDs to error messages. + /// * `Err(NativeError)` – If the component UUID is not registered. + /// + /// # Errors + /// Returns a configuration error if the component is not found. + pub fn validate( + &self, + origin: &stypes::SessionAction, + target: &Uuid, + fields: &[stypes::Field], + ) -> Result, stypes::NativeError> { + let (_, descriptor) = self + .descriptors() + .into_iter() + .find(|(uuid, _)| *uuid == target) + .ok_or(stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Configuration, + message: Some(format!("Fail to find component {target}")), + })?; + descriptor.validate(origin, fields) + } + + /// Returns a list of source and parser components that are compatible with the given session context + /// and can be used without explicit configuration. + /// + /// This method is designed to support "quick start" scenarios, where the user initiates a session + /// (e.g., opening a file) without manually selecting or configuring components. It filters available + /// sources and parsers based on the following criteria: + /// + /// - The component must declare compatibility with the provided [`SessionAction`]. + /// - The component must support default options (i.e., `get_default_options()` returns `Some`). + /// - The component's IO type must match the detected IO data type inferred from the session context + /// (e.g., plain text or raw binary). + /// + /// This method is typically used to automatically select a default setup when the user opens + /// a file (such as a text or DLT file), allowing immediate parsing and display without prompting + /// for configuration. + /// + /// # Arguments + /// + /// * `origin` - The session context (e.g., a file or set of files) used to infer compatibility and IO type. + /// + /// # Returns + /// + /// A [`ComponentsList`] containing identifiers of all matching sources and parsers. + /// If no fully compatible combination is found, an empty list is returned. + /// + /// # Errors + /// + /// Returns a [`NativeError`] if binary detection fails for one or more files. + /// + /// # Behavior + /// + /// - For a single file: detects whether the file is binary or plain text. + /// - For multiple files: only returns components if all are plain text, or all are binary. + /// Mixed types result in an empty list. + /// - For unsupported session types (e.g., `Source`, `ExportRaw`), returns an empty list. + pub fn get_compatible_setup( + &self, + origin: &stypes::SessionAction, + ) -> Result { + let io_type = match origin { + stypes::SessionAction::File(filename) => { + if is_path_binary(filename).map_err(|err| NativeError::io(&err.to_string()))? { + stypes::IODataType::Raw + } else { + stypes::IODataType::PlaitText + } + } + stypes::SessionAction::Files(files) => { + let mut bin_count = 0; + for filename in files { + bin_count += if is_path_binary(filename) + .map_err(|err| NativeError::io(&err.to_string()))? + { + 1 + } else { + 0 + }; + } + if files.len() == bin_count { + stypes::IODataType::Raw + } else if !files.is_empty() && bin_count == 0 { + stypes::IODataType::PlaitText + } else { + return Ok(stypes::ComponentsList::default()); + } + } + stypes::SessionAction::Source | stypes::SessionAction::ExportRaw(..) => { + return Ok(stypes::ComponentsList::default()) + } + }; + let sources: Vec = self + .sources + .iter() + .filter_map(|(_, entity)| { + if entity.is_compatible(origin) + && entity.get_default_options(origin).is_some() + && entity.ident().io == io_type + { + Some(entity.ident()) + } else { + None + } + }) + .collect(); + let parsers: Vec = self + .parsers + .iter() + .filter_map(|(_, entity)| { + if entity.is_compatible(origin) + && entity.get_default_options(origin).is_some() + && entity.ident().io == io_type + { + Some(entity.ident()) + } else { + None + } + }) + .collect(); + + Ok(stypes::ComponentsList { parsers, sources }) + } + + /// Returns the default configuration options for the specified component, or an error if none are available. + /// + /// This method retrieves the default options for a parser or source component in the context of + /// the given [`SessionAction`]. It is used to determine whether the component can be instantiated + /// automatically, without requiring manual configuration by the user. + /// + /// Unlike earlier versions, this method no longer returns `Option`. If the component does not + /// support default options (i.e., it **must** be explicitly configured), the method returns a + /// [`NativeError`] indicating that it cannot be used in automatic setup scenarios. + /// + /// # Arguments + /// + /// * `origin` - The session context used to evaluate compatibility and scope. + /// * `target` - The UUID of the component for which default options are requested. + /// + /// # Returns + /// + /// * `Ok(FieldList)` - A list of fields to be used for instantiating the component. + /// If the list is empty, it means the component has no configurable options, but can still be used by default. + /// * `Err(NativeError)` - If the component: + /// - is not registered, + /// - does not support default options (and therefore cannot be used blindly). + /// + /// # Semantics + /// + /// - Empty `FieldList` (`FieldList(vec![])`) means the component has no settings but **is safe to use as default**. + /// - An error means the component **requires explicit configuration** and must not be auto-selected. + /// + /// # Errors + /// + /// Returns a [`NativeError`] with kind `Configuration` if: + /// - The component is not found, + /// - The component does not support default options in the given session context. + pub fn get_default_options( + &self, + origin: &stypes::SessionAction, + target: &Uuid, + ) -> Result { + let (_, descriptor) = self + .descriptors() + .into_iter() + .find(|(uuid, _)| *uuid == target) + .ok_or(stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Configuration, + message: Some(format!("Fail to find component {target}")), + })?; + descriptor + .get_default_options(origin) + .map(|fields| stypes::FieldList(fields)) + .ok_or(stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Configuration, + message: Some(format!( + "Component {target} doesn't support default options" + )), + }) + } + + /// Attempts to create instances of a parser and source component based on the session setup. + /// + /// This method creates fully initialized, ready-to-use instances of parser and source + /// based on the provided configuration. It also returns a `SessionDescriptor` + /// that contains display identifiers for tracking the session state. + /// + /// # Arguments + /// * `options` – Session setup data including parser/source UUIDs and user-defined field values. + /// + /// # Returns + /// * `Ok((SessionDescriptor, S, P))` – A tuple with session metadata, the source instance, and the parser instance. + /// * `Err(NativeError)` – If any of the components cannot be found or fail to initialize. + /// + /// # Errors + /// Returns configuration errors if components are missing or initialization fails. + pub fn setup( + &self, + options: &stypes::SessionSetup, + ) -> Result<(SessionDescriptor, Sources, Parsers), stypes::NativeError> { + let Some(parser_factory) = self.parsers.get(&options.parser.uuid) else { + return Err(stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Configuration, + message: Some(format!("Fail to find parser {}", options.parser.uuid)), + }); + }; + let Some(source_factory) = self.sources.get(&options.source.uuid) else { + return Err(stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Configuration, + message: Some(format!("Fail to find source {}", options.source.uuid)), + }); + }; + let mut descriptor = SessionDescriptor::new( + source_factory.bound_ident(&options.origin, &options.source.fields), + parser_factory.bound_ident(&options.origin, &options.parser.fields), + ); + let Some((parser, desc)) = + parser_factory.create(&options.origin, &options.parser.fields)? + else { + return Err(stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Configuration, + message: Some(format!("Fail to init parser {}", options.parser.uuid)), + }); + }; + descriptor.set_parser_desc(desc); + let Some((source, desc)) = + source_factory.create(&options.origin, &options.source.fields)? + else { + return Err(stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Configuration, + message: Some(format!("Fail to init source {}", options.source.uuid)), + }); + }; + descriptor.set_source_desc(desc); + Ok((descriptor, source, parser)) + } + + fn descriptors(&self) -> Vec<(&Uuid, &dyn CommonDescriptor)> { + [ + self.sources + .iter() + .map(|(uuid, entity)| (uuid, entity.as_ref() as &dyn CommonDescriptor)) + .collect::>(), + self.parsers + .iter() + .map(|(uuid, entity)| (uuid, entity.as_ref() as &dyn CommonDescriptor)) + .collect::>(), + ] + .concat() + } +} + +impl fmt::Debug for Register { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Register") + } +} diff --git a/application/apps/indexer/session/Cargo.toml b/application/apps/indexer/session/Cargo.toml index de703dc6d..57fb1ee82 100644 --- a/application/apps/indexer/session/Cargo.toml +++ b/application/apps/indexer/session/Cargo.toml @@ -10,30 +10,74 @@ crossbeam-channel.workspace = true dirs.workspace = true dlt-core = { workspace = true, features = ["statistics", "serialization"] } envvars = { workspace = true } -file-tools = { path = "../addons/file-tools" } futures.workspace = true -indexer_base = { path = "../indexer_base" } lazy_static.workspace = true log.workspace = true -merging = { path = "../merging" } mime_guess = "2.0" -parsers = { path = "../parsers" } -processor = { path = "../processor" } rustc-hash = "2.1" -stypes = { path = "../stypes", features=["rustcore"] } serde = { workspace = true , features = ["derive"] } serde_json.workspace = true -serialport = "4.6" -sources = { path = "../sources" } +serialport.workspace = true thiserror.workspace = true tokio = { workspace = true , features = ["full"] } tokio-stream.workspace = true tokio-util.workspace = true uuid = { workspace = true , features = ["serde", "v4"] } walkdir.workspace = true -plugins_host = {path = "../plugins_host/"} + +indexer_base = { path = "../indexer_base" } +plugins_host = { path = "../plugins_host/"} +descriptor = { path = "../descriptor" } +stypes = { path = "../stypes", features=["rustcore"] } +parsers = { path = "../parsers" } +processor = { path = "../processor" } +sources = { path = "../sources" } +merging = { path = "../merging" } +register = { path = "../register" } +file-tools = { path = "../addons/file-tools" } [dev-dependencies] lazy_static.workspace = true tempfile.workspace = true insta.workspace = true +env_logger.workspace = true +criterion = { workspace = true, features = ["async_tokio"] } +plugins_host = {path = "../plugins_host/"} +toml.workspace = true + +[[bench]] +name = "dlt_producer" +harness = false + +[[bench]] +name = "someip_producer" +harness = false + +[[bench]] +name = "someip_legacy_producer" +harness = false + +[[bench]] +name = "text_producer" +harness = false + +[[bench]] +name = "plugin_praser_producer" +harness = false + +[[bench]] +name = "mocks_once_producer" +harness = false + +[[bench]] +name = "mocks_once_parallel" +harness = false + +[[bench]] +name = "mocks_multi_producer" +harness = false + +[[bench]] +name = "mocks_multi_parallel" +harness = false + diff --git a/application/apps/indexer/sources/benches/bench_utls.rs b/application/apps/indexer/session/benches/bench_utls.rs similarity index 93% rename from application/apps/indexer/sources/benches/bench_utls.rs rename to application/apps/indexer/session/benches/bench_utls.rs index 0b43834f3..4d9853aa2 100644 --- a/application/apps/indexer/sources/benches/bench_utls.rs +++ b/application/apps/indexer/session/benches/bench_utls.rs @@ -10,8 +10,9 @@ use std::{ }; use criterion::Criterion; -use parsers::{LogMessage, MessageStreamItem}; -use sources::{binary::raw::BinaryByteSource, producer::MessageProducer}; +use parsers::{LogRecordOutput, LogRecordsBuffer, MessageStreamItem}; +use processor::producer::MessageProducer; +use sources::binary::raw::BinaryByteSource; pub const INPUT_SOURCE_ENV_VAR: &str = "CHIPMUNK_BENCH_SOURCE"; pub const CONFIG_ENV_VAR: &str = "CHIPMUNK_BENCH_CONFIG"; @@ -73,11 +74,13 @@ pub struct ProducerCounter { /// Run producer until the end converting messages into strings too, while counting all the /// different types of producer outputs to avoid unwanted compiler optimizations. -pub async fn run_producer(mut producer: MessageProducer) -> ProducerCounter +pub async fn run_producer<'a, P, S, B>( + mut producer: MessageProducer<'a, P, S, B>, +) -> ProducerCounter where - P: parsers::Parser, - B: sources::ByteSource, - T: LogMessage, + P: parsers::Parser, + S: sources::ByteSource, + B: LogRecordsBuffer, { let mut counter = ProducerCounter::default(); diff --git a/application/apps/indexer/sources/benches/dlt_producer.rs b/application/apps/indexer/session/benches/dlt_producer.rs similarity index 97% rename from application/apps/indexer/sources/benches/dlt_producer.rs rename to application/apps/indexer/session/benches/dlt_producer.rs index c803b6b0b..00954b335 100644 --- a/application/apps/indexer/sources/benches/dlt_producer.rs +++ b/application/apps/indexer/session/benches/dlt_producer.rs @@ -1,11 +1,11 @@ use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; +use processor::producer::MessageProducer; use std::path::PathBuf; use bench_utls::{ bench_standrad_config, create_binary_bytesource, get_config, read_binary, run_producer, }; use parsers::dlt::{self, DltParser}; -use sources::producer::MessageProducer; mod bench_utls; diff --git a/application/apps/indexer/sources/benches/mocks/mock_parser.rs b/application/apps/indexer/session/benches/mocks/mock_parser.rs similarity index 70% rename from application/apps/indexer/sources/benches/mocks/mock_parser.rs rename to application/apps/indexer/session/benches/mocks/mock_parser.rs index ac1dea36d..2cd3dcdab 100644 --- a/application/apps/indexer/sources/benches/mocks/mock_parser.rs +++ b/application/apps/indexer/session/benches/mocks/mock_parser.rs @@ -1,7 +1,6 @@ -use std::{fmt::Display, iter, marker::PhantomData}; +use std::{iter, marker::PhantomData}; -use parsers::{Attachment, LogMessage, Parser}; -use serde::Serialize; +use parsers::{Attachment, LogRecordOutput, ParseReturnIterator, Parser}; use std::hint::black_box; /// Empty type used as phantom data with mock parser to indicate that its [`Parser::parse()`] @@ -56,69 +55,44 @@ impl MockParser { } } -#[derive(Debug, Serialize)] -/// Return type of [`Parser::parse()`] method for [`MockParser`] -pub struct MockMessage { - content: String, -} - -impl Display for MockMessage { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.content) - } -} - -impl LogMessage for MockMessage { - fn to_writer( - &self, - writer: &mut W, - ) -> Result { - let len = self.content.len(); - writer.write_all(self.content.as_bytes())?; - Ok(len) - } -} - impl MockParser { /// Method to replicate parse behavior with artificial if statements with black boxes to avoid /// any uncounted compiler optimization. #[inline(never)] - fn inner_parse( + fn inner_parse<'a>( counter: usize, max_count: usize, - input: &[u8], + input: &'a [u8], _timestamp: Option, - ) -> Result<(usize, Option>), parsers::Error> { + ) -> Result<(usize, Option>), parsers::ParserError> { // Return `Eof` Once the counter reaches max_count. if counter >= max_count { - const ERR: parsers::Error = parsers::Error::Eof; + const ERR: parsers::ParserError = parsers::ParserError::Eof; return Err(black_box(ERR)); } // Unnecessary check to convince the compiler that we are using the input. if input.is_empty() { - return Err(black_box(parsers::Error::Eof)); + return Err(black_box(parsers::ParserError::Eof)); } const MSG: &str = "msg"; // Unnecessary checks to convince the compiler that all return options are possible. if black_box(50) > black_box(60) { - Err(parsers::Error::Incomplete) + Err(parsers::ParserError::Incomplete) } else if black_box(50) > black_box(0) { // Only this value will be always returned if the calls counter still smaller than // the max value. Ok(( black_box(input.len()), - Some(parsers::ParseYield::Message(MockMessage { - content: black_box(MSG).into(), - })), + Some(LogRecordOutput::Message(black_box(MSG).into())), )) } else if black_box(20) > black_box(30) { Ok(( black_box(input.len()), - Some(parsers::ParseYield::Attachment(Attachment { + Some(LogRecordOutput::Attachment(Attachment { size: black_box(10), name: String::from(black_box(MSG)), data: Vec::new(), @@ -130,19 +104,17 @@ impl MockParser { } else { Ok(( black_box(input.len()), - Some(parsers::ParseYield::MessageAndAttachment(( - MockMessage { - content: black_box(MSG).into(), - }, - Attachment { + Some(LogRecordOutput::Multiple(vec![ + LogRecordOutput::Message(black_box(MSG).into()), + LogRecordOutput::Attachment(Attachment { size: black_box(10), name: String::from(black_box(MSG)), data: Vec::new(), messages: Vec::new(), created_date: None, modified_date: None, - }, - ))), + }), + ])), )) } } @@ -150,49 +122,35 @@ impl MockParser { // NOTE: Methods within trait implementation have inner non-async function that should never be // inline and the trait method should be always inline. This reduces the noise in the benchmarks. -impl Parser for MockParser { +impl Parser for MockParser { /// This will keep returning a valid item result withing an [`iter::once`] until the counter /// reaches max count then it will be return [`parsers::Error::Eof`] - fn parse( - &mut self, - input: &[u8], - timestamp: Option, - ) -> Result< - impl Iterator>)>, - parsers::Error, - > { + fn parse<'a>(&mut self, input: &'a [u8], timestamp: Option) -> ParseReturnIterator<'a> { self.counter += 1; let item = Self::inner_parse(self.counter, self.max_count, input, timestamp)?; - Ok(iter::once(item)) + Ok(Box::new(iter::once(item))) } } // NOTE: Methods within trait implementation have inner non-async function that should never be // inline and the trait method should be always inline. This reduces the noise in the benchmarks. -impl Parser for MockParser { +impl Parser for MockParser { /// This will keep returning an iterator of multiple valid items until the counter reaches max /// count then it will be return [`parsers::Error::Eof`] - fn parse( - &mut self, - input: &[u8], - timestamp: Option, - ) -> Result< - impl Iterator>)>, - parsers::Error, - > { + fn parse<'a>(&mut self, input: &'a [u8], timestamp: Option) -> ParseReturnIterator<'a> { self.counter += 1; if self.counter >= self.max_count { - const ERR: parsers::Error = parsers::Error::Eof; + const ERR: parsers::ParserError = parsers::ParserError::Eof; return Err(black_box(ERR)); } const REPEAT: usize = 10; let mut counter = 0; - let res = iter::from_fn(move || { + let iter = iter::from_fn(move || { counter += 1; if counter < black_box(REPEAT) { Self::inner_parse(self.counter, self.max_count, input, timestamp).ok() @@ -200,7 +158,6 @@ impl Parser for MockParser { None } }); - black_box(Ok(res)) } } diff --git a/application/apps/indexer/sources/benches/mocks/mock_source.rs b/application/apps/indexer/session/benches/mocks/mock_source.rs similarity index 94% rename from application/apps/indexer/sources/benches/mocks/mock_source.rs rename to application/apps/indexer/session/benches/mocks/mock_source.rs index 58744a20a..d465ffd2e 100644 --- a/application/apps/indexer/sources/benches/mocks/mock_source.rs +++ b/application/apps/indexer/session/benches/mocks/mock_source.rs @@ -61,10 +61,10 @@ impl ByteSource for MockByteSource { async fn load( &mut self, _filter: Option<&sources::SourceFilter>, - ) -> Result, sources::Error> { + ) -> Result, sources::SourceError> { #[inline(never)] - fn inner() -> Result, sources::Error> { - const AA: Result, sources::Error> = + fn inner() -> Result, sources::SourceError> { + const AA: Result, sources::SourceError> = Ok(Some(sources::ReloadInfo { available_bytes: 5, newly_loaded_bytes: 5, diff --git a/application/apps/indexer/session/benches/mocks/mock_writer.rs b/application/apps/indexer/session/benches/mocks/mock_writer.rs new file mode 100644 index 000000000..ef7daad22 --- /dev/null +++ b/application/apps/indexer/session/benches/mocks/mock_writer.rs @@ -0,0 +1,54 @@ +use parsers::{Attachment, COLUMN_SENTINAL, LogRecordOutput, LogRecordsBuffer}; + +pub struct MockLogsBuffer { + messages: Vec, + attachments: Vec, + id: u16, +} + +impl MockLogsBuffer { + pub fn new(id: u16) -> Self { + Self { + messages: Vec::new(), + attachments: Vec::new(), + id, + } + } +} + +impl LogRecordsBuffer for MockLogsBuffer { + fn append(&mut self, record: LogRecordOutput<'_>) { + match record { + LogRecordOutput::Raw(inner) => { + self.messages.push( + inner + .iter() + .map(|b| format!("{:02X}", b)) + .collect::(), + ); + } + LogRecordOutput::Message(msg) => { + self.messages.push(msg.to_string()); + } + LogRecordOutput::Columns(inner) => { + self.messages.push(inner.join(&COLUMN_SENTINAL.to_string())); + } + LogRecordOutput::Multiple(inner) => { + for rec in inner { + self.append(rec); + } + } + LogRecordOutput::Attachment(inner) => { + self.attachments.push(inner); + } + } + } + + async fn flush(&mut self) -> Result<(), stypes::NativeError> { + Ok(()) + } + + fn get_source_id(&self) -> u16 { + self.id + } +} diff --git a/application/apps/indexer/sources/benches/mocks/mod.rs b/application/apps/indexer/session/benches/mocks/mod.rs similarity index 66% rename from application/apps/indexer/sources/benches/mocks/mod.rs rename to application/apps/indexer/session/benches/mocks/mod.rs index f03babe3a..775430d4d 100644 --- a/application/apps/indexer/sources/benches/mocks/mod.rs +++ b/application/apps/indexer/session/benches/mocks/mod.rs @@ -1,2 +1,3 @@ pub mod mock_parser; pub mod mock_source; +pub mod mock_writer; diff --git a/application/apps/indexer/sources/benches/mocks_multi_parallel.rs b/application/apps/indexer/session/benches/mocks_multi_parallel.rs similarity index 98% rename from application/apps/indexer/sources/benches/mocks_multi_parallel.rs rename to application/apps/indexer/session/benches/mocks_multi_parallel.rs index 9bead87b0..84fd452ab 100644 --- a/application/apps/indexer/sources/benches/mocks_multi_parallel.rs +++ b/application/apps/indexer/session/benches/mocks_multi_parallel.rs @@ -10,7 +10,7 @@ use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use bench_utls::{bench_standrad_config, run_producer}; use mocks::{mock_parser::MockParser, mock_source::MockByteSource}; -use sources::producer::MessageProducer; +use processor::producer::MessageProducer; mod bench_utls; mod mocks; diff --git a/application/apps/indexer/sources/benches/mocks_multi_producer.rs b/application/apps/indexer/session/benches/mocks_multi_producer.rs similarity index 97% rename from application/apps/indexer/sources/benches/mocks_multi_producer.rs rename to application/apps/indexer/session/benches/mocks_multi_producer.rs index ed3874931..072f2cb6e 100644 --- a/application/apps/indexer/sources/benches/mocks_multi_producer.rs +++ b/application/apps/indexer/session/benches/mocks_multi_producer.rs @@ -6,7 +6,7 @@ use bench_utls::{bench_standrad_config, run_producer}; use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use mocks::{mock_parser::MockParser, mock_source::MockByteSource}; -use sources::producer::MessageProducer; +use processor::producer::MessageProducer; mod bench_utls; mod mocks; diff --git a/application/apps/indexer/sources/benches/mocks_once_parallel.rs b/application/apps/indexer/session/benches/mocks_once_parallel.rs similarity index 98% rename from application/apps/indexer/sources/benches/mocks_once_parallel.rs rename to application/apps/indexer/session/benches/mocks_once_parallel.rs index 373811f2f..ab5018ab3 100644 --- a/application/apps/indexer/sources/benches/mocks_once_parallel.rs +++ b/application/apps/indexer/session/benches/mocks_once_parallel.rs @@ -7,7 +7,7 @@ use bench_utls::{bench_standrad_config, run_producer}; use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use mocks::{mock_parser::MockParser, mock_source::MockByteSource}; -use sources::producer::MessageProducer; +use processor::producer::MessageProducer; use std::hint::black_box; mod bench_utls; mod mocks; diff --git a/application/apps/indexer/sources/benches/mocks_once_producer.rs b/application/apps/indexer/session/benches/mocks_once_producer.rs similarity index 97% rename from application/apps/indexer/sources/benches/mocks_once_producer.rs rename to application/apps/indexer/session/benches/mocks_once_producer.rs index 6ab489640..ce4591e14 100644 --- a/application/apps/indexer/sources/benches/mocks_once_producer.rs +++ b/application/apps/indexer/session/benches/mocks_once_producer.rs @@ -6,7 +6,7 @@ use bench_utls::{bench_standrad_config, run_producer}; use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use mocks::{mock_parser::MockParser, mock_source::MockByteSource}; -use sources::producer::MessageProducer; +use processor::producer::MessageProducer; mod bench_utls; mod mocks; diff --git a/application/apps/indexer/sources/benches/plugin_praser_producer.rs b/application/apps/indexer/session/benches/plugin_praser_producer.rs similarity index 97% rename from application/apps/indexer/sources/benches/plugin_praser_producer.rs rename to application/apps/indexer/session/benches/plugin_praser_producer.rs index 5abf29cf0..20ac6f47f 100644 --- a/application/apps/indexer/sources/benches/plugin_praser_producer.rs +++ b/application/apps/indexer/session/benches/plugin_praser_producer.rs @@ -3,7 +3,7 @@ use plugins_host::PluginsParser; use std::hint::black_box; use bench_utls::{bench_standrad_config, create_binary_bytesource, read_binary, run_producer}; -use sources::producer::MessageProducer; +use processor::producer::MessageProducer; mod bench_utls; diff --git a/application/apps/indexer/sources/benches/someip_legacy_producer.rs b/application/apps/indexer/session/benches/someip_legacy_producer.rs similarity index 94% rename from application/apps/indexer/sources/benches/someip_legacy_producer.rs rename to application/apps/indexer/session/benches/someip_legacy_producer.rs index a7f42387c..32fc44953 100644 --- a/application/apps/indexer/sources/benches/someip_legacy_producer.rs +++ b/application/apps/indexer/session/benches/someip_legacy_producer.rs @@ -5,7 +5,8 @@ use std::{io::Cursor, path::PathBuf}; use bench_utls::{bench_standrad_config, get_config, read_binary, run_producer}; use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; use parsers::someip::SomeipParser; -use sources::{binary::pcap::legacy::PcapLegacyByteSource, producer::MessageProducer}; +use processor::producer::MessageProducer; +use sources::binary::pcap::legacy::PcapLegacyByteSource; /// This benchmark covers parsing from SomeIP file using [`PcapLegacyByteSource`] byte source. /// It supports providing the path for a fibex file as additional configuration. diff --git a/application/apps/indexer/sources/benches/someip_producer.rs b/application/apps/indexer/session/benches/someip_producer.rs similarity index 95% rename from application/apps/indexer/sources/benches/someip_producer.rs rename to application/apps/indexer/session/benches/someip_producer.rs index 4e94abbc3..c0126dfd9 100644 --- a/application/apps/indexer/sources/benches/someip_producer.rs +++ b/application/apps/indexer/session/benches/someip_producer.rs @@ -3,7 +3,8 @@ use std::{io::Cursor, path::PathBuf}; use bench_utls::{bench_standrad_config, get_config, read_binary, run_producer}; use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; use parsers::someip::SomeipParser; -use sources::{binary::pcap::ng::PcapngByteSource, producer::MessageProducer}; +use processor::producer::MessageProducer; +use sources::binary::pcap::ng::PcapngByteSource; mod bench_utls; diff --git a/application/apps/indexer/sources/benches/text_producer.rs b/application/apps/indexer/session/benches/text_producer.rs similarity index 96% rename from application/apps/indexer/sources/benches/text_producer.rs rename to application/apps/indexer/session/benches/text_producer.rs index 74f764184..946fefd9f 100644 --- a/application/apps/indexer/sources/benches/text_producer.rs +++ b/application/apps/indexer/session/benches/text_producer.rs @@ -2,7 +2,7 @@ use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; use bench_utls::{bench_standrad_config, create_binary_bytesource, read_binary, run_producer}; use parsers::text::StringTokenizer; -use sources::producer::MessageProducer; +use processor::producer::MessageProducer; mod bench_utls; diff --git a/application/apps/indexer/session/src/handlers/export_raw.rs b/application/apps/indexer/session/src/handlers/export_raw.rs deleted file mode 100644 index da1e3a2d5..000000000 --- a/application/apps/indexer/session/src/handlers/export_raw.rs +++ /dev/null @@ -1,200 +0,0 @@ -use crate::{operations::OperationResult, state::SessionStateAPI}; -use indexer_base::config::IndexSection; -use log::debug; -use parsers::{ - LogMessage, Parser, - dlt::{DltParser, fmt::FormatOptions}, - someip::SomeipParser, - text::StringTokenizer, -}; -use plugins_host::PluginsParser; -use processor::export::{ExportError, export_raw}; -use sources::{ - ByteSource, - binary::{ - pcap::{legacy::PcapLegacyByteSource, ng::PcapngByteSource}, - raw::BinaryByteSource, - }, - producer::MessageProducer, -}; -use std::{ - fs::File, - path::{Path, PathBuf}, -}; -use tokio_util::sync::CancellationToken; - -pub async fn execute_export( - cancel: &CancellationToken, - state: SessionStateAPI, - out_path: PathBuf, - ranges: Vec>, -) -> OperationResult { - debug!("RUST: ExportRaw operation is requested"); - let observed = state.get_executed_holder().await?; - if !observed.is_file_based_export_possible() { - return Err(stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::Configuration, - message: Some(String::from( - "For current collection of observing operation raw export isn't possible.", - )), - }); - } - let mut indexes = ranges - .iter() - .map(IndexSection::from) - .collect::>(); - let count = observed.get_files().len(); - for (i, (parser, file_format, filename)) in observed.get_files().iter().enumerate() { - if indexes.is_empty() { - break; - } - let read = if let Some(read) = assing_source( - filename, - &out_path, - parser, - file_format, - &indexes, - i != (count - 1), - cancel, - ) - .await? - { - read - } else { - return Ok(Some(false)); - }; - indexes.iter_mut().for_each(|index| index.left(read)); - indexes = indexes - .into_iter() - .filter(|index| !index.is_empty()) - .collect::>(); - } - Ok(Some(true)) -} - -async fn assing_source( - src: &PathBuf, - dest: &Path, - parser: &stypes::ParserType, - file_format: &stypes::FileFormat, - sections: &Vec, - read_to_end: bool, - cancel: &CancellationToken, -) -> Result, stypes::NativeError> { - let reader = File::open(src).map_err(|e| stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::Io, - message: Some(format!("Fail open file {}: {}", src.to_string_lossy(), e)), - })?; - match file_format { - stypes::FileFormat::Binary | stypes::FileFormat::Text => { - export( - dest, - parser, - BinaryByteSource::new(reader), - sections, - read_to_end, - cancel, - ) - .await - } - stypes::FileFormat::PcapNG => { - export( - dest, - parser, - PcapngByteSource::new(reader)?, - sections, - read_to_end, - cancel, - ) - .await - } - stypes::FileFormat::PcapLegacy => { - export( - dest, - parser, - PcapLegacyByteSource::new(reader)?, - sections, - read_to_end, - cancel, - ) - .await - } - } -} - -async fn export( - dest: &Path, - parser: &stypes::ParserType, - source: S, - sections: &Vec, - read_to_end: bool, - cancel: &CancellationToken, -) -> Result, stypes::NativeError> { - match parser { - stypes::ParserType::Plugin(settings) => { - let parser = PluginsParser::initialize( - &settings.plugin_path, - &settings.general_settings, - settings.plugin_configs.clone(), - ) - .await?; - let producer = MessageProducer::new(parser, source); - export_runner(producer, dest, sections, read_to_end, false, cancel).await - } - stypes::ParserType::SomeIp(settings) => { - let parser = if let Some(files) = settings.fibex_file_paths.as_ref() { - SomeipParser::from_fibex_files(files.iter().map(PathBuf::from).collect()) - } else { - SomeipParser::new() - }; - let producer = MessageProducer::new(parser, source); - export_runner(producer, dest, sections, read_to_end, false, cancel).await - } - stypes::ParserType::Dlt(settings) => { - let fmt_options = Some(FormatOptions::from(settings.tz.as_ref())); - let parser = DltParser::new( - settings.filter_config.as_ref().map(|f| f.into()), - settings.fibex_metadata.as_ref(), - fmt_options.as_ref(), - None, - settings.with_storage_header, - ); - let producer = MessageProducer::new(parser, source); - export_runner(producer, dest, sections, read_to_end, false, cancel).await - } - stypes::ParserType::Text(()) => { - let producer = MessageProducer::new(StringTokenizer {}, source); - export_runner(producer, dest, sections, read_to_end, true, cancel).await - } - } -} - -pub async fn export_runner( - producer: MessageProducer, - dest: &Path, - sections: &Vec, - read_to_end: bool, - text_file: bool, - cancel: &CancellationToken, -) -> Result, stypes::NativeError> -where - T: LogMessage + Sized, - P: Parser, - D: ByteSource, -{ - export_raw(producer, dest, sections, read_to_end, text_file, cancel) - .await - .map_or_else( - |err| match err { - ExportError::Cancelled => Ok(None), - _ => Err(stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::UnsupportedFileType, - message: Some(format!("{err}")), - }), - }, - |read| Ok(Some(read)), - ) -} diff --git a/application/apps/indexer/session/src/handlers/mod.rs b/application/apps/indexer/session/src/handlers/mod.rs index f0301268e..2b3c96175 100644 --- a/application/apps/indexer/session/src/handlers/mod.rs +++ b/application/apps/indexer/session/src/handlers/mod.rs @@ -1,7 +1,5 @@ -pub mod export_raw; pub mod extract; pub mod observe; -mod observing; pub mod search; pub mod search_values; pub mod sleep; diff --git a/application/apps/indexer/session/src/handlers/observe.rs b/application/apps/indexer/session/src/handlers/observe.rs deleted file mode 100644 index 863183399..000000000 --- a/application/apps/indexer/session/src/handlers/observe.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::{ - handlers::observing, - operations::{OperationAPI, OperationResult}, - state::SessionStateAPI, -}; -use log::error; -use sources::sde::SdeReceiver; - -pub async fn start_observing( - operation_api: OperationAPI, - state: SessionStateAPI, - mut options: stypes::ObserveOptions, - rx_sde: Option, -) -> OperationResult<()> { - if let stypes::ParserType::Dlt(ref mut settings) = options.parser { - settings.load_fibex_metadata(); - }; - if let Err(err) = state.add_executed_observe(options.clone()).await { - error!("Fail to store observe options: {err:?}"); - } - match &options.origin { - stypes::ObserveOrigin::File(uuid, file_origin, filename) => { - let (is_text, session_file_origin) = ( - matches!(options.parser, stypes::ParserType::Text(())), - state.get_session_file_origin().await?, - ); - match session_file_origin { - Some(origin) if origin.is_linked() => Err(stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::Configuration, - message: Some(String::from( - "Cannot observe file, because session is linked to other text file", - )), - }), - Some(origin) if !origin.is_linked() && is_text => { - // Session file was created and some files/streams were opened already. We should check for text files - // to prevent attempt to link session with text file. Using concat instead - observing::concat::concat_files( - operation_api, - state, - &[(uuid.clone(), file_origin.clone(), filename.clone())], - &options.parser, - ) - .await - } - _ => { - observing::file::observe_file( - operation_api, - state, - uuid, - file_origin, - filename, - &options.parser, - ) - .await - } - } - } - stypes::ObserveOrigin::Concat(files) => { - if files.is_empty() { - Err(stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::Configuration, - message: Some(String::from("No files are defined for Concat operation")), - }) - } else { - observing::concat::concat_files(operation_api, state, files, &options.parser).await - } - } - stypes::ObserveOrigin::Stream(uuid, transport) => { - observing::stream::observe_stream( - operation_api, - state, - uuid, - transport, - &options.parser, - rx_sde, - ) - .await - } - } -} diff --git a/application/apps/indexer/session/src/handlers/observe/export_raw.rs b/application/apps/indexer/session/src/handlers/observe/export_raw.rs new file mode 100644 index 000000000..29b9ee229 --- /dev/null +++ b/application/apps/indexer/session/src/handlers/observe/export_raw.rs @@ -0,0 +1,120 @@ +use crate::operations::{OperationAPI, OperationResult}; +use log::debug; +use parsers::api::*; +use processor::producer::MessageProducer; +use sources::api::*; + +use std::{ + fs::File, + io::{self, BufWriter, Write}, + path::Path, +}; +use tokio::select; + +pub struct ExportLogsBuffer { + file_buffer: BufWriter, + bytes_buffer: Vec, + index: usize, + ranges: Vec>, +} + +impl ExportLogsBuffer { + pub fn new>( + filename: P, + ranges: Vec>, + ) -> io::Result { + let filename = filename.as_ref(); + let out_file = if filename.exists() { + std::fs::OpenOptions::new().append(true).open(filename)? + } else { + std::fs::File::create(filename)? + }; + Ok(Self { + file_buffer: BufWriter::new(out_file), + bytes_buffer: Vec::new(), + index: 0, + ranges, + }) + } +} + +impl LogRecordsBuffer for ExportLogsBuffer { + fn append(&mut self, record: LogRecordOutput<'_>) { + if !self.ranges.is_empty() { + // TODO: we can optimize index search + if !self + .ranges + .iter() + .any(|range| range.contains(&(self.index as u64))) + { + // Skip record because it's not in a range + self.index += 1; + } + } + self.index += 1; + match record { + LogRecordOutput::Raw(inner) => { + self.bytes_buffer.extend_from_slice(inner); + } + LogRecordOutput::Message(inner) => { + self.bytes_buffer.extend_from_slice(inner.as_bytes()); + self.bytes_buffer.push(b'\n'); + } + LogRecordOutput::Columns(inner) => { + let mut items = inner.into_iter(); + if let Some(first_item) = items.next() { + self.bytes_buffer.extend_from_slice(first_item.as_bytes()); + for item in items { + self.bytes_buffer.push(parsers::api::COLUMN_SENTINAL as u8); + self.bytes_buffer.extend_from_slice(item.as_bytes()); + } + } + self.bytes_buffer.push(b'\n'); + } + LogRecordOutput::Multiple(inner) => { + for rec in inner { + self.append(rec); + } + } + LogRecordOutput::Attachment(att) => { + log::error!( + "Log attachments provided in raw export. Attachment name: {}", + att.name + ); + } + } + } + async fn flush(&mut self) -> Result<(), stypes::NativeError> { + if !self.bytes_buffer.is_empty() { + self.file_buffer.write_all(&self.bytes_buffer)?; + self.bytes_buffer.clear() + } + self.file_buffer.flush()?; + + Ok(()) + } + fn get_source_id(&self) -> u16 { + 0 + } +} + +pub async fn run_producer( + operation_api: OperationAPI, + mut producer: MessageProducer<'_, P, S, B>, +) -> OperationResult { + operation_api.processing(); + let cancel = operation_api.cancellation_token(); + while select! { + next_from_stream = producer.read_next_segment() => next_from_stream, + _ = async { + cancel.cancelled().await; + debug!("exporting operation has been cancelled"); + } => None, + } + .is_some() + { + // Do nothing. Writing happens on MessageProducer level + } + debug!("exporting is done"); + Ok(Some(!cancel.is_cancelled())) +} diff --git a/application/apps/indexer/session/src/handlers/observe/mod.rs b/application/apps/indexer/session/src/handlers/observe/mod.rs new file mode 100644 index 000000000..d954eef19 --- /dev/null +++ b/application/apps/indexer/session/src/handlers/observe/mod.rs @@ -0,0 +1,69 @@ +mod export_raw; +mod session; + +use crate::{ + operations::{OperationAPI, OperationResult}, + state::SessionStateAPI, +}; +use processor::producer::{MessageProducer, sde::*}; +use register::Register; +use std::sync::Arc; +use stypes::{SessionAction, SessionSetup}; + +pub async fn observing( + operation_api: OperationAPI, + state: SessionStateAPI, + options: SessionSetup, + components: Arc, + rx_sde: Option, +) -> OperationResult<()> { + match &options.origin { + SessionAction::File(..) => { + let (desciptor, source, parser) = components.setup(&options)?; + let mut logs_buffer = session::LogsBuffer::new( + state.clone(), + state.add_source(operation_api.id(), desciptor).await?, + ); + let producer = MessageProducer::new(parser, source, &mut logs_buffer); + Ok(session::run_producer(operation_api, state, producer, None, None).await?) + } + SessionAction::Source => { + let (desciptor, source, parser) = components.setup(&options)?; + let mut logs_buffer = session::LogsBuffer::new( + state.clone(), + state.add_source(operation_api.id(), desciptor).await?, + ); + let producer = MessageProducer::new(parser, source, &mut logs_buffer); + Ok(session::run_producer(operation_api, state, producer, None, rx_sde).await?) + } + SessionAction::Files(files) => { + // Replacement of concat feature + for file in files { + let (desciptor, source, parser) = + components.setup(&options.inherit(SessionAction::File(file.to_owned())))?; + let mut logs_buffer = session::LogsBuffer::new( + state.clone(), + state.add_source(operation_api.id(), desciptor).await?, + ); + let producer = MessageProducer::new(parser, source, &mut logs_buffer); + session::run_producer(operation_api.clone(), state.clone(), producer, None, None) + .await?; + } + Ok(Some(())) + } + SessionAction::ExportRaw(files, ranges, output) => { + // We are creating one single buffer for all files to keep tracking ranges and current index + let mut logs_buffer = export_raw::ExportLogsBuffer::new(output, ranges.clone())?; + for file in files { + if operation_api.cancellation_token().is_cancelled() { + return Ok(Some(())); + } + let (_, source, parser) = + components.setup(&options.inherit(SessionAction::File(file.to_owned())))?; + let producer = MessageProducer::new(parser, source, &mut logs_buffer); + export_raw::run_producer(operation_api.clone(), producer).await?; + } + Ok(Some(())) + } + } +} diff --git a/application/apps/indexer/session/src/handlers/observe/session.rs b/application/apps/indexer/session/src/handlers/observe/session.rs new file mode 100644 index 000000000..d6908ac59 --- /dev/null +++ b/application/apps/indexer/session/src/handlers/observe/session.rs @@ -0,0 +1,211 @@ +use crate::{ + operations::{OperationAPI, OperationResult}, + state::SessionStateAPI, + tail, +}; +use log::{trace, warn}; +use parsers::api; +use parsers::api::*; +use processor::producer::{MessageProducer, MessageStreamItem, sde::*}; +use sources::api::*; +use std::time::Instant; +use tokio::{select, sync::mpsc::Receiver}; + +/// A buffer for accumulating log data before writing to a session file. +/// +/// This buffer is linked to a session file via the `SessionStateAPI`. +/// It accumulates updates and sends them when `flush()` is called. +/// +/// It uses two internal stores to manage data: +/// - `buffer` accumulates textual log entries. +/// - `attachments` stores associated `Attachment` objects. These are sent +/// only after the text buffer is flushed to ensure synchronization. +pub struct LogsBuffer { + /// Communication channel to the session file. + state: SessionStateAPI, + + /// Text buffer for log messages. Since session files are text-based, + /// the buffered data is stored as a `String`. + text_buffer: String, + + /// Buffer for received attachments. These are queued and sent only after + /// the buffered log messages have been flushed, preserving the logical + /// order between messages and attachments. + attachments: Vec, + + /// Unique identifier for the data source. This is used on the client side + /// to visually group or distinguish data streams. + id: u16, +} + +impl LogsBuffer { + pub fn new(state: SessionStateAPI, id: u16) -> Self { + Self { + state, + text_buffer: String::new(), + attachments: Vec::new(), + id, + } + } +} + +impl LogRecordsBuffer for LogsBuffer { + fn append(&mut self, record: LogRecordOutput<'_>) { + match record { + LogRecordOutput::Raw(inner) => { + // TODO: Needs to be optimized. Also this use-case doesn't seem normal, should be some logs + // because during observe we do not expect raw data + self.text_buffer + .push_str(&inner.iter().map(|b| format!("{b:02X}")).collect::()); + self.text_buffer.push('\n'); + } + LogRecordOutput::Message(msg) => { + self.text_buffer.push_str(&msg); + self.text_buffer.push('\n'); + } + LogRecordOutput::Columns(inner) => { + let mut items = inner.into_iter(); + if let Some(first_item) = items.next() { + self.text_buffer.push_str(&first_item); + for item in items { + self.text_buffer.push(api::COLUMN_SENTINAL); + self.text_buffer.push_str(&item); + } + } + self.text_buffer.push('\n'); + } + LogRecordOutput::Multiple(inner) => { + for rec in inner { + self.append(rec); + } + } + LogRecordOutput::Attachment(inner) => { + self.attachments.push(inner); + } + } + } + + async fn flush(&mut self) -> Result<(), stypes::NativeError> { + if !self.text_buffer.is_empty() { + // Creates an owned string from current buffer then clean the current. This operation + // produces one mem_copy command for the needed bytes only while preserving + // the capacity of the intermediate buffer. + // Rust doesn't provide safe way to move bytes between strings without replacing + // the whole string, forcing us to allocate the full capacity of the buffer on each + // iteration (which could backfire in the internal buffer gets too long in one of the + // iterations). + let msgs = String::from(&self.text_buffer); + self.text_buffer.clear(); + self.state + .write_session_file(self.get_source_id(), msgs) + .await?; + } + for attachment in self.attachments.drain(..) { + // TODO: send with 1 call + self.state.add_attachment(attachment)?; + } + Ok(()) + } + + fn get_source_id(&self) -> u16 { + self.id + } +} + +enum Next { + Parsed(usize, MessageStreamItem), + Waiting, + Sde(SdeMsg), +} + +/// ********************************************************************************************** +/// For Ammar: +/// +/// Currently, the use of select! requires careful handling of cancel-safety in all methods involved. +/// Instead of solving the rather complex problem of full cancel-safety, I suggest localizing the issue: +/// +/// * Remove all non-essential functionality from the select! block and use it only to listen for +/// messages. +/// * File reading can be performed in a loop that regularly checks the state of the SDE queue. +/// * Once the end of the file is reached, we can begin listening with select! for changes in the data +/// source, updates from SDE, and termination signals. +/// ********************************************************************************************** + +pub async fn run_producer( + operation_api: OperationAPI, + state: SessionStateAPI, + mut producer: MessageProducer<'_, P, S, B>, + mut rx_tail: Option>>, + mut rx_sde: Option, +) -> OperationResult<()> { + use log::debug; + state.set_session_file(None).await?; + operation_api.processing(); + let cancel = operation_api.cancellation_token(); + let cancel_on_tail = cancel.clone(); + let started = Instant::now(); + + while let Some(next) = select! { + next_from_stream = async { + producer.read_next_segment().await + .map(|(consumed, results)|Next::Parsed(consumed, results)) + .or_else(|| Some(Next::Waiting)) + } => next_from_stream, + Some(sde_msg) = async { + if let Some(rx_sde) = rx_sde.as_mut() { + rx_sde.recv().await + } else { + None + } + } => Some(Next::Sde(sde_msg)), + _ = cancel.cancelled() => None, + } { + match next { + Next::Parsed(_consumed, results) => match results { + MessageStreamItem::Parsed(..) => { + //Just continue + } + MessageStreamItem::Done => { + state.flush_session_file().await?; + state.file_read().await?; + warn!( + "observe, message stream is done in {} ms", + started.elapsed().as_millis() + ); + } + MessageStreamItem::Skipped => { + trace!("observe: skipped a message"); + } + }, + Next::Waiting => { + if !state.is_closing() { + state.flush_session_file().await?; + } + if let Some(mut rx_tail) = rx_tail.take() { + if select! { + next_from_stream = rx_tail.recv() => { + if let Some(result) = next_from_stream { + result.is_err() + } else { + true + } + }, + _ = cancel_on_tail.cancelled() => true, + } { + break; + } + } else { + break; + } + } + Next::Sde((msg, tx_response)) => { + let sde_res = producer.sde_income(msg).await.map_err(|e| e.to_string()); + if tx_response.send(sde_res).is_err() { + warn!("Fail to send back message from source"); + } + } + } + } + debug!("listen done"); + Ok(None) +} diff --git a/application/apps/indexer/session/src/handlers/observing/concat.rs b/application/apps/indexer/session/src/handlers/observing/concat.rs deleted file mode 100644 index c12073962..000000000 --- a/application/apps/indexer/session/src/handlers/observing/concat.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::{ - operations::{OperationAPI, OperationResult}, - state::SessionStateAPI, -}; -use sources::binary::{ - pcap::{legacy::PcapLegacyByteSource, ng::PcapngByteSource}, - raw::BinaryByteSource, -}; -use std::{fs::File, path::PathBuf}; - -#[allow(clippy::type_complexity)] -pub async fn concat_files( - operation_api: OperationAPI, - state: SessionStateAPI, - files: &[(String, stypes::FileFormat, PathBuf)], - parser: &stypes::ParserType, -) -> OperationResult<()> { - for file in files.iter() { - let (uuid, _file_type, _filename) = file; - state.add_source(uuid).await?; - } - for file in files.iter() { - let (uuid, file_type, filename) = file; - let source_id = state.get_source(uuid).await?.ok_or(stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::Io, - message: Some(format!( - "Cannot find source id for file {} with alias {}", - filename.to_string_lossy(), - uuid, - )), - })?; - let input_file = File::open(filename).map_err(|e| stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::Io, - message: Some(format!( - "Fail open file {}: {}", - filename.to_string_lossy(), - e - )), - })?; - match file_type { - stypes::FileFormat::Binary => { - super::run_source( - operation_api.clone(), - state.clone(), - BinaryByteSource::new(input_file), - source_id, - parser, - None, - None, - ) - .await? - } - stypes::FileFormat::PcapLegacy => { - super::run_source( - operation_api.clone(), - state.clone(), - PcapLegacyByteSource::new(input_file)?, - source_id, - parser, - None, - None, - ) - .await? - } - stypes::FileFormat::PcapNG => { - super::run_source( - operation_api.clone(), - state.clone(), - PcapngByteSource::new(input_file)?, - source_id, - parser, - None, - None, - ) - .await? - } - stypes::FileFormat::Text => { - super::run_source( - operation_api.clone(), - state.clone(), - BinaryByteSource::new(input_file), - source_id, - parser, - None, - None, - ) - .await? - } - }; - } - Ok(Some(())) -} diff --git a/application/apps/indexer/session/src/handlers/observing/file.rs b/application/apps/indexer/session/src/handlers/observing/file.rs deleted file mode 100644 index c3a85de0a..000000000 --- a/application/apps/indexer/session/src/handlers/observing/file.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::{ - operations::{OperationAPI, OperationResult}, - state::SessionStateAPI, - tail, -}; -use sources::binary::{ - pcap::{legacy::PcapLegacyByteSource, ng::PcapngByteSource}, - raw::BinaryByteSource, -}; -use std::{fs::File, path::Path}; -use tokio::{ - join, select, - sync::mpsc::{Receiver, Sender, channel}, -}; - -#[allow(clippy::type_complexity)] -pub async fn observe_file( - operation_api: OperationAPI, - state: SessionStateAPI, - uuid: &str, - file_format: &stypes::FileFormat, - filename: &Path, - parser: &stypes::ParserType, -) -> OperationResult<()> { - let source_id = state.add_source(uuid).await?; - let (tx_tail, mut rx_tail): ( - Sender>, - Receiver>, - ) = channel(1); - match file_format { - stypes::FileFormat::Binary => { - let source = BinaryByteSource::new(input_file(filename)?); - let (_, listening) = join!( - tail::track(filename, tx_tail, operation_api.cancellation_token()), - super::run_source( - operation_api, - state, - source, - source_id, - parser, - None, - Some(rx_tail) - ) - ); - listening - } - stypes::FileFormat::PcapLegacy => { - let source = PcapLegacyByteSource::new(input_file(filename)?)?; - let (_, listening) = join!( - tail::track(filename, tx_tail, operation_api.cancellation_token()), - super::run_source( - operation_api, - state, - source, - source_id, - parser, - None, - Some(rx_tail) - ) - ); - listening - } - stypes::FileFormat::PcapNG => { - let source = PcapngByteSource::new(input_file(filename)?)?; - let (_, listening) = join!( - tail::track(filename, tx_tail, operation_api.cancellation_token()), - super::run_source( - operation_api, - state, - source, - source_id, - parser, - None, - Some(rx_tail) - ) - ); - listening - } - stypes::FileFormat::Text => { - state.set_session_file(Some(filename.to_path_buf())).await?; - // Grab main file content - state.update_session(source_id).await?; - operation_api.processing(); - // Confirm: main file content has been read - state.file_read().await?; - // Switching to tail - let cancel = operation_api.cancellation_token(); - let (result, tracker) = join!( - async { - let result = select! { - res = async move { - while let Some(update) = rx_tail.recv().await { - update.map_err(|err| stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::Interrupted, - message: Some(err.to_string()), - })?; - state.update_session(source_id).await?; - } - Ok(()) - } => res, - _ = cancel.cancelled() => Ok(()) - }; - result - }, - tail::track(filename, tx_tail, operation_api.cancellation_token()), - ); - result - .and_then(|_| { - tracker.map_err(|e| stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::Interrupted, - message: Some(format!("Tailing error: {e}")), - }) - }) - .map(|_| None) - } - } -} - -fn input_file(filename: &Path) -> Result { - File::open(filename).map_err(|e| stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::Io, - message: Some(format!( - "Fail open file {}: {}", - filename.to_string_lossy(), - e - )), - }) -} diff --git a/application/apps/indexer/session/src/handlers/observing/mod.rs b/application/apps/indexer/session/src/handlers/observing/mod.rs deleted file mode 100644 index d753dd8c8..000000000 --- a/application/apps/indexer/session/src/handlers/observing/mod.rs +++ /dev/null @@ -1,238 +0,0 @@ -use std::path::PathBuf; - -use crate::{ - operations::{OperationAPI, OperationResult}, - state::SessionStateAPI, - tail, -}; -use log::trace; -use parsers::{ - LogMessage, MessageStreamItem, ParseYield, Parser, - dlt::{DltParser, fmt::FormatOptions}, - someip::{FibexMetadata as FibexSomeipMetadata, SomeipParser}, - text::StringTokenizer, -}; -use plugins_host::PluginsParser; -use sources::{ - ByteSource, - producer::MessageProducer, - sde::{SdeMsg, SdeReceiver}, -}; -use tokio::{ - select, - sync::mpsc::Receiver, - time::{Duration, timeout}, -}; - -enum Next<'a, T: LogMessage> { - Items(&'a mut Vec<(usize, MessageStreamItem)>), - Timeout, - Waiting, - Sde(SdeMsg), -} - -pub mod concat; -pub mod file; -pub mod stream; - -pub const FLUSH_TIMEOUT_IN_MS: u128 = 500; - -pub async fn run_source( - operation_api: OperationAPI, - state: SessionStateAPI, - source: S, - source_id: u16, - parser: &stypes::ParserType, - rx_sde: Option, - rx_tail: Option>>, -) -> OperationResult<()> { - let cancel = operation_api.cancellation_token(); - - // Actual function is wrapped here in order to react on errors and cancel other tasks - // running concurrently. - let operation_result = run_source_intern( - operation_api, - state, - source, - source_id, - parser, - rx_sde, - rx_tail, - ) - .await; - - if operation_result.is_err() && !cancel.is_cancelled() { - cancel.cancel(); - } - - operation_result -} - -/// Contains all implementation details for running the source and the producer in the session -async fn run_source_intern( - operation_api: OperationAPI, - state: SessionStateAPI, - source: S, - source_id: u16, - parser: &stypes::ParserType, - rx_sde: Option, - rx_tail: Option>>, -) -> OperationResult<()> { - match parser { - stypes::ParserType::Plugin(settings) => { - let parser = PluginsParser::initialize( - &settings.plugin_path, - &settings.general_settings, - settings.plugin_configs.clone(), - ) - .await?; - let producer = MessageProducer::new(parser, source); - run_producer(operation_api, state, source_id, producer, rx_tail, rx_sde).await - } - stypes::ParserType::SomeIp(settings) => { - let someip_parser = match &settings.fibex_file_paths { - Some(paths) => { - SomeipParser::from_fibex_files(paths.iter().map(PathBuf::from).collect()) - } - None => SomeipParser::new(), - }; - let producer = MessageProducer::new(someip_parser, source); - run_producer(operation_api, state, source_id, producer, rx_tail, rx_sde).await - } - stypes::ParserType::Text(()) => { - let producer = MessageProducer::new(StringTokenizer {}, source); - run_producer(operation_api, state, source_id, producer, rx_tail, rx_sde).await - } - stypes::ParserType::Dlt(settings) => { - let fmt_options = Some(FormatOptions::from(settings.tz.as_ref())); - let someip_metadata = settings.fibex_file_paths.as_ref().and_then(|paths| { - FibexSomeipMetadata::from_fibex_files(paths.iter().map(PathBuf::from).collect()) - }); - let dlt_parser = DltParser::new( - settings.filter_config.as_ref().map(|f| f.into()), - settings.fibex_metadata.as_ref(), - fmt_options.as_ref(), - someip_metadata.as_ref(), - settings.with_storage_header, - ); - let producer = MessageProducer::new(dlt_parser, source); - run_producer(operation_api, state, source_id, producer, rx_tail, rx_sde).await - } - } -} - -async fn run_producer, S: ByteSource>( - operation_api: OperationAPI, - state: SessionStateAPI, - source_id: u16, - mut producer: MessageProducer, - mut rx_tail: Option>>, - mut rx_sde: Option, -) -> OperationResult<()> { - use log::debug; - state.set_session_file(None).await?; - operation_api.processing(); - let cancel = operation_api.cancellation_token(); - let cancel_on_tail = cancel.clone(); - while let Some(next) = select! { - next_from_stream = async { - match timeout(Duration::from_millis(FLUSH_TIMEOUT_IN_MS as u64), producer.read_next_segment()).await { - Ok(items) => { - if let Some(items) = items { - Some(Next::Items(items)) - } else { - Some(Next::Waiting) - } - }, - Err(_) => Some(Next::Timeout), - } - } => next_from_stream, - Some(sde_msg) = async { - if let Some(rx_sde) = rx_sde.as_mut() { - rx_sde.recv().await - } else { - None - } - } => Some(Next::Sde(sde_msg)), - - _ = cancel.cancelled() => None, - } { - match next { - Next::Items(items) => { - // Iterating over references is more efficient than using `drain(..)`, even though - // we clone the attachments below. With ownership, `mem_copy()` would still be called - // to move the item into the attachment vector. Cloning avoids the overhead of - // `drain(..)`, especially since `items` is cleared on each iteration anyway. - for (_, item) in items { - match item { - MessageStreamItem::Item(ParseYield::Message(item)) => { - state - .write_session_file(source_id, format!("{item}\n")) - .await?; - } - MessageStreamItem::Item(ParseYield::MessageAndAttachment(( - item, - attachment, - ))) => { - state - .write_session_file(source_id, format!("{item}\n")) - .await?; - state.add_attachment(attachment.to_owned())?; - } - MessageStreamItem::Item(ParseYield::Attachment(attachment)) => { - state.add_attachment(attachment.to_owned())?; - } - MessageStreamItem::Done => { - trace!("observe, message stream is done"); - state.flush_session_file().await?; - state.file_read().await?; - } - // MessageStreamItem::FileRead => { - // state.file_read().await?; - // } - MessageStreamItem::Skipped => { - trace!("observe: skipped a message"); - } - MessageStreamItem::Incomplete => { - trace!("observe: incomplete message"); - } - MessageStreamItem::Empty => { - trace!("observe: empty message"); - } - } - } - } - Next::Timeout => { - if !state.is_closing() { - state.flush_session_file().await?; - } - } - Next::Waiting => { - if let Some(mut rx_tail) = rx_tail.take() { - if select! { - next_from_stream = rx_tail.recv() => { - if let Some(result) = next_from_stream { - result.is_err() - } else { - true - } - }, - _ = cancel_on_tail.cancelled() => true, - } { - break; - } - } else { - break; - } - } - Next::Sde((msg, tx_response)) => { - let sde_res = producer.sde_income(msg).await.map_err(|e| e.to_string()); - if tx_response.send(sde_res).is_err() { - log::warn!("Fail to send back message from source"); - } - } - } - } - debug!("listen done"); - Ok(None) -} diff --git a/application/apps/indexer/session/src/handlers/observing/stream.rs b/application/apps/indexer/session/src/handlers/observing/stream.rs deleted file mode 100644 index 0a2e44201..000000000 --- a/application/apps/indexer/session/src/handlers/observing/stream.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::{ - handlers::observing, - operations::{OperationAPI, OperationResult}, - state::SessionStateAPI, -}; -use sources::{ - command::process::ProcessSource, - serial::serialport::SerialSource, - socket::{tcp::TcpSource, udp::UdpSource}, -}; - -use super::SdeReceiver; - -pub async fn observe_stream( - operation_api: OperationAPI, - state: SessionStateAPI, - uuid: &str, - transport: &stypes::Transport, - parser: &stypes::ParserType, - rx_sde: Option, -) -> OperationResult<()> { - let source_id = state.add_source(uuid).await?; - match transport { - stypes::Transport::UDP(settings) => { - let udp_source = UdpSource::new(&settings.bind_addr, settings.multicast.clone()) - .await - .map_err(|e| stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::Interrupted, - message: Some(format!("{e}")), - })?; - observing::run_source( - operation_api, - state, - udp_source, - source_id, - parser, - rx_sde, - None, - ) - .await - } - stypes::Transport::TCP(settings) => { - let tcp_source = TcpSource::new(&settings.bind_addr, None, None) - .await - .map_err(|e| stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::Interrupted, - message: Some(format!("{e}")), - })?; - observing::run_source( - operation_api, - state, - tcp_source, - source_id, - parser, - rx_sde, - None, - ) - .await - } - stypes::Transport::Serial(settings) => { - let serial_source = SerialSource::new(settings).map_err(|e| stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::Interrupted, - message: Some(format!("{e}")), - })?; - observing::run_source( - operation_api, - state, - serial_source, - source_id, - parser, - rx_sde, - None, - ) - .await - } - stypes::Transport::Process(settings) => { - let process_source = ProcessSource::new( - settings.command.clone(), - settings.cwd.clone(), - settings.envs.clone(), - ) - .await - .map_err(|e| stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::Interrupted, - message: Some(format!("{e}")), - })?; - observing::run_source( - operation_api, - state, - process_source, - source_id, - parser, - rx_sde, - None, - ) - .await - } - } -} diff --git a/application/apps/indexer/session/src/lib.rs b/application/apps/indexer/session/src/lib.rs index 81f6bb982..f2116016c 100644 --- a/application/apps/indexer/session/src/lib.rs +++ b/application/apps/indexer/session/src/lib.rs @@ -2,6 +2,7 @@ mod handlers; pub mod operations; pub mod paths; pub mod progress; +pub mod register; pub mod session; pub mod state; pub mod tail; diff --git a/application/apps/indexer/session/src/operations.rs b/application/apps/indexer/session/src/operations.rs index b4ecfb4de..35ebd193d 100644 --- a/application/apps/indexer/session/src/operations.rs +++ b/application/apps/indexer/session/src/operations.rs @@ -1,12 +1,14 @@ use crate::{handlers, state::SessionStateAPI, tracker::OperationTrackerAPI}; use log::{debug, error, warn}; use merging::merger::FileMergeOptions; +use processor::producer::sde::{SdeReceiver, SdeSender}; use processor::search::filter::SearchFilter; +use register::Register; use serde::Serialize; -use sources::sde::{SdeReceiver, SdeSender}; use std::{ ops::RangeInclusive, path::PathBuf, + sync::Arc, time::{SystemTime, UNIX_EPOCH}, }; @@ -72,7 +74,7 @@ impl Operation { #[derive(Debug)] #[allow(clippy::large_enum_variant)] pub enum OperationKind { - Observe(stypes::ObserveOptions), + Observe(stypes::SessionSetup, Arc), Search { filters: Vec, }, @@ -105,10 +107,6 @@ pub enum OperationKind { /// An optional string used as the field delimiter within each record in output file. Defaults can be applied if `None`. delimiter: Option, }, - ExportRaw { - out_path: PathBuf, - ranges: Vec>, - }, Extract { filters: Vec, }, @@ -140,11 +138,10 @@ impl std::fmt::Display for OperationKind { f, "{}", match self { - OperationKind::Observe(_) => "Observing", + OperationKind::Observe(..) => "Observing", OperationKind::Search { .. } => "Searching", OperationKind::SearchValues { .. } => "Searching values", OperationKind::Export { .. } => "Exporting", - OperationKind::ExportRaw { .. } => "Exporting as Raw", OperationKind::Extract { .. } => "Extracting", OperationKind::Map { .. } => "Mapping", OperationKind::Values { .. } => "Values", @@ -305,10 +302,16 @@ impl OperationAPI { api.started(); let operation_str = &format!("{}", operation.kind); match operation.kind { - OperationKind::Observe(options) => { + OperationKind::Observe(options, components) => { api.finish( - handlers::observe::start_observing(api.clone(), state, options, rx_sde) - .await, + handlers::observe::observing( + api.clone(), + state, + options, + components, + rx_sde, + ) + .await, operation_str, ) .await; @@ -353,20 +356,6 @@ impl OperationAPI { ) .await; } - OperationKind::ExportRaw { out_path, ranges } => { - api.finish( - handlers::export_raw::execute_export( - &api.cancellation_token(), - state, - out_path, - ranges, - ) - .await - .map(|v| v.map(stypes::ResultBool)), - operation_str, - ) - .await; - } OperationKind::Extract { filters } => { let session_file = if let Some(session_file) = session_file { session_file diff --git a/application/apps/indexer/session/src/register/api.rs b/application/apps/indexer/session/src/register/api.rs new file mode 100644 index 000000000..7dcbd694e --- /dev/null +++ b/application/apps/indexer/session/src/register/api.rs @@ -0,0 +1,231 @@ +use std::collections::HashMap; + +use descriptor::{LazyLoadingResult, LazyLoadingTaskMeta}; +use stypes::{Ident, NativeError, SessionAction}; +use tokio::sync::oneshot; +use uuid::Uuid; + +/// Represents the various API commands for managing component sessions and operations. +/// +/// The `Api` enum defines different commands used within the system, including both public and internal APIs. +/// These commands handle session management, lazy loading operations, and communication between components and clients. +#[derive(Debug)] +pub enum Api { + /// Initiates the shutdown procedure for the component session controller. + /// + /// This API is public and can be accessed by the client. + /// Upon successful shutdown, it sends a confirmation signal through the provided `oneshot::Sender<()>`. + Shutdown(oneshot::Sender<()>), + + /// Triggered when the lazy loading of a component's configuration schema has completed. + /// + /// This API is internal and not accessible to clients. + /// + /// # Arguments + /// + /// * `Uuid` - A unique identifier of the component. + /// * `LazyLoadingTaskMeta` - Metadata associated with the lazy loading task. + /// * `Result` - The result of the lazy loading operation, indicating success or failure. + LazyTaskComplete( + Uuid, + LazyLoadingTaskMeta, + Result, + ), + + /// Cancels the ongoing lazy loading of configuration schemas for one or more components. + /// + /// This API is triggered by the client when a previously requested lazy loading operation needs to be stopped. + /// The client does not have knowledge of the internal task identifiers, so it provides a vector of field IDs. + /// The controller is responsible for identifying the corresponding loading tasks and canceling them. + /// + /// # Important + /// + /// The client does not receive any confirmation of the cancellation result. + /// + /// # Arguments + /// + /// * `Vec` - A vector of field IDs for which the loading tasks should be canceled. + CancelLoading(Vec), + + /// Requests the configuration schema for the specified components. + /// + /// This API is used by the client to retrieve the settings of one or more components. + /// + /// # Arguments + /// + /// * `origin` - The source type within which the client intends to use the components. + /// * `targets` - A vector of component UUIDs whose configuration schemas are being requested. + /// * `tx` - A one-shot sender used to deliver the result back to the client. + /// + /// # Result + /// + /// * `Result` - The result containing the list of component options or an error. + GetOptions { + origin: SessionAction, + targets: Vec, + tx: oneshot::Sender>, + }, + + /// Requests the render supported by parser + /// + /// This API is used by the client to retrieve the render to use. + /// + /// # Arguments + /// + /// * `Uuid` - Uuid of parser. + /// * `tx` - A one-shot sender used to deliver the result back to the client. + /// + /// # Result + /// + /// * `Result, NativeError>` - The result containing the render or an error. + /// + /// # Note + /// If component doesn't have render, returns `None` + GetOutputRender( + Uuid, + oneshot::Sender, NativeError>>, + ), + + /// Requests the ident of component + /// + /// This API is used by the client to retrieve the identification of component parser or source. + /// + /// # Arguments + /// + /// * `Uuid` - Uuid of component (parser / source). + /// * `tx` - A one-shot sender used to deliver the result back to the client. + /// + /// # Result + /// + /// * `Option` - Ident of target component. + /// + /// # Note + /// If component doesn't exist, returns `None` + GetIdent(Uuid, oneshot::Sender>), + + /// Retrieves a list of components available in the system. + /// + /// This API is invoked by the client when it needs to get the list of available components of a specific type. + /// + /// # Arguments + /// + /// * `SessionAction` - The origin type indicating the context in which the components will be used. + /// * `stypes::ComponentType` - The type of components to retrieve (e.g., parser, source). + /// * `oneshot::Sender, NativeError>>` - A sender channel for delivering the result back to the client. + GetComponents( + SessionAction, + stypes::ComponentType, + oneshot::Sender, NativeError>>, + ), + /// A request to check whether a source supports the Source Data Exchange (SDE) mechanism. + /// + /// This variant is used to query if the specified source, identified by its `Uuid`, allows data + /// to be sent to it in the given session context. The result of the check is returned asynchronously + /// through the provided `oneshot::Sender`. + /// + /// # Arguments + /// + /// * `SessionAction` - The session context in which the SDE support should be evaluated. + /// * `Uuid` - The unique identifier of the source component. + /// * `oneshot::Sender>` - A channel sender used to return + /// the result of the check asynchronously: + /// - `Ok(true)` if SDE is supported, + /// - `Ok(false)` if not supported, + /// - `Err(NativeError)` if the source was not found or another error occurred. + IsSdeSupported( + SessionAction, + Uuid, + oneshot::Sender>, + ), + /// API request to resolve all components compatible with a given session context. + /// + /// This variant is used to automatically select sources and parsers that: + /// - Are compatible with the provided [`SessionAction`], + /// - Support default options (i.e., can be used without user configuration), + /// - Match the inferred IO data type (e.g., plain text or binary). + /// + /// This request is typically used to implement default behavior when the user + /// opens a file or data stream without specifying any components. + /// + /// # Fields + /// + /// - [`SessionAction`] - The session context used to infer IO type and compatibility. + /// - [`oneshot::Sender>`] - The response channel + /// returning a list of matching component identifiers or an error. + GetCompatibleSetup( + SessionAction, + oneshot::Sender>, + ), + /// API request to obtain the default configuration options for a specific component. + /// + /// This variant is used to retrieve the default `FieldList` associated with a source or parser + /// in the context of the given [`SessionAction`]. If the component cannot be used + /// without explicit configuration (i.e., it does not support defaults), an error is returned. + /// + /// # Fields + /// + /// - [`SessionAction`] - The session context that affects the component’s option set. + /// - [`Uuid`] - The unique identifier of the component. + /// - [`oneshot::Sender>`] - The response channel returning + /// the default options or an error if the component is not suitable for automatic setup. + GetDefaultOptions( + SessionAction, + Uuid, + oneshot::Sender>, + ), + /// Validates the configuration for correctness. + /// + /// # Arguments + /// + /// * `SessionAction` - The origin type indicating the context in which the validation is performed. + /// * `Uuid` - The identifier of the component (parser, source). + /// * `Vec` - A list of configuration field values. + /// * `oneshot::Sender, NativeError>>` - A sender channel for delivering validation results to the client. + /// + /// # Returns + /// + /// `HashMap`: + /// * Key (`String`) - The field's identifier. + /// * Value (`String`) - The error message related to the field. + /// + /// If all fields are valid and have no errors, an empty `HashMap` will be returned. + Validate( + SessionAction, + Uuid, + Vec, + oneshot::Sender, NativeError>>, + ), + /// Get all information of the installed plugins . + InstalledPluginsList(oneshot::Sender>), + /// Get all information of invalid plugins . + InvalidPluginsList(oneshot::Sender>), + /// Get the directory paths (considered ID) for installed plugins. + InstalledPluginsPaths(oneshot::Sender>), + /// Get the directory paths (considered ID) for invalid plugins. + InvalidPluginsPaths(oneshot::Sender>), + /// Get all info for the installed plugin with provided directory path (considered ID) + InstalledPluginInfo( + String, + oneshot::Sender, NativeError>>, + ), + /// Get all info for the invalid plugin with provided directory path (considered ID) + InvalidPluginInfo( + String, + oneshot::Sender, NativeError>>, + ), + /// Retrieves runtime data for a plugin located at the specified path. + PluginRunData( + String, + oneshot::Sender, NativeError>>, + ), + /// Reload all the plugins from their directory. + ReloadPlugins(oneshot::Sender>), + /// Adds a plugin with the given directory path and optional plugin type. + AddPlugin( + String, + Option, + oneshot::Sender>, + ), + /// Removes the plugin with the given directory path. + RemovePlugin(String, oneshot::Sender>), +} diff --git a/application/apps/indexer/session/src/register/mod.rs b/application/apps/indexer/session/src/register/mod.rs new file mode 100644 index 000000000..33d46d671 --- /dev/null +++ b/application/apps/indexer/session/src/register/mod.rs @@ -0,0 +1,798 @@ +mod api; +mod plugins; + +use api::*; +use descriptor::{LazyLoadingResult, LazyLoadingTaskMeta}; +use log::{debug, error}; +use register::Register; +use std::{collections::HashMap, sync::Arc}; +use tokio::{ + sync::{ + RwLock, + mpsc::{UnboundedReceiver, UnboundedSender, error::SendError, unbounded_channel}, + oneshot::{self, error::RecvError}, + }, + task::{self, JoinHandle}, +}; +use uuid::Uuid; + +/// A controller responsible for managing all available components in the system (such as parsers, sources, etc.). +/// +/// This structure acts as a central registry that stores and manages the state of all components throughout +/// the entire application lifecycle. It is instantiated only once and remains active for the application's duration. +/// +/// # Fields +/// +/// * `tx_api` - An unbounded sender used for communicating with the API. It allows sending API-related messages +/// or commands to manage components or respond to client requests. +pub struct SessionRegister { + tx_api: UnboundedSender, +} + +impl SessionRegister { + /// Creates a new components session. + /// + /// This method initializes the `SessionRegister` and returns a tuple containing the session instance + /// and an unbounded receiver. The receiver acts as a feedback channel, allowing the delivery of + /// asynchronous operation results to the client. + /// + /// # Returns + /// + /// * `Ok((Self, UnboundedReceiver))` - A tuple containing the newly created + /// session and the receiver for asynchronous callbacks. + /// * `Err(stypes::NativeError)` - An error if the session could not be created. + /// + /// # Usage + /// + /// This method is asynchronous and should be awaited when called. The returned receiver can be used + /// to listen for callback events related to the components' operations. + pub async fn new() + -> Result<(Self, UnboundedReceiver), stypes::NativeError> { + // TODO: Plugins manager is used temporally here in initial phase and we should consider + // moving it to its own module. Reasons: + // * It doesn't need parallelism for most of task. + // * It'll need different state and locking management for downloading plugins, Updating + // caches etc... + let plugins_manager = plugins::load_manager().await?; + + let (tx_api, mut rx_api): (UnboundedSender, UnboundedReceiver) = + unbounded_channel(); + let (tx_callback_events, rx_callback_events): ( + UnboundedSender, + UnboundedReceiver, + ) = unbounded_channel(); + let mut register: Register = Register::new(); + // Registre parsers + parsers_registration(&mut register)?; + // Registre sources + plugins_manager.register_plugins(&mut register)?; + let plugins_manager = Arc::new(RwLock::new(plugins_manager)); + sources_registration(&mut register)?; + let tx_api_inner = tx_api.clone(); + let session = Self { tx_api }; + task::spawn(async move { + debug!("Session is started"); + let mut tasks: HashMap)> = HashMap::new(); + while let Some(msg) = rx_api.recv().await { + match msg { + Api::GetOutputRender(uuid, tx) => { + log_if_err(tx.send(register.get_output_render(&uuid))); + } + Api::GetIdent(uuid, tx) => { + log_if_err(tx.send(register.get_ident(&uuid))); + } + Api::GetOptions { + origin, + targets, + tx, + } => { + let mut options = match register.get_options(origin, targets) { + Ok(options) => options, + Err(err) => { + log_if_err(tx.send(Err(err))); + continue; + } + }; + // Send static fields + log_if_err( + tx.send(Ok(stypes::ComponentsOptionsList { + options: options + .iter_mut() + .map(|(uuid, opt)| (*uuid, opt.extract_spec())) + .collect(), + })), + ); + // Loading lazy if exist + for (_, opt) in options.into_iter() { + if let Some(mut loading_task) = opt.lazy { + // If exists, request lazy source fields + let meta = loading_task.get_meta(); + let uuid = meta.uuid; + let tx_api = tx_api_inner.clone(); + tasks.insert( + uuid, + ( + meta, + task::spawn(async move { + log_if_err(tx_api.send(Api::LazyTaskComplete( + uuid, + loading_task.get_meta(), + loading_task.wait().await, + ))); + }), + ), + ); + } + } + } + // Delivery lazy fields to client. + Api::LazyTaskComplete(uuid, meta, results) => { + tasks.remove(&uuid); + match results { + Ok(LazyLoadingResult::Fields(fields)) => { + let (success, fail): (Vec<_>, Vec<_>) = + fields.into_iter().partition(|result| result.is_ok()); + let fields: Vec = success + .into_iter() + .filter_map(|result| result.ok()) + .collect(); + let errors: Vec = + fail.into_iter().filter_map(|result| result.err()).collect(); + if !fields.is_empty() { + log_if_err(tx_callback_events.send( + stypes::CallbackOptionsEvent::LoadingDone { + owner: meta.owner(), + fields, + }, + )); + } + if !errors.is_empty() { + log_if_err(tx_callback_events.send( + stypes::CallbackOptionsEvent::LoadingErrors { + owner: meta.owner(), + errors, + }, + )); + } + } + Ok(..) => { + // Task has been cancelled + log_if_err(tx_callback_events.send( + stypes::CallbackOptionsEvent::LoadingCancelled { + owner: meta.owner(), + fields: meta.fields, + }, + )); + continue; + } + Err(err) => { + error!("Fail to load lazy field with: {err}"); + log_if_err(tx_callback_events.send( + stypes::CallbackOptionsEvent::LoadingError { + owner: meta.owner(), + error: err.to_string(), + fields: meta.fields, + }, + )); + } + } + } + Api::GetComponents(origin, ty, tx) => { + log_if_err(tx.send(Ok(register.get_components(ty, origin)))); + } + Api::IsSdeSupported(origin, uuid, tx) => { + log_if_err(tx.send(register.is_sde_supported(&uuid, &origin))); + } + Api::GetCompatibleSetup(origin, tx) => { + log_if_err(tx.send(register.get_compatible_setup(&origin))); + } + Api::GetDefaultOptions(origin, target, tx) => { + log_if_err(tx.send(register.get_default_options(&origin, &target))); + } + // Client doesn't need any more field data. Loading task should be cancelled + Api::CancelLoading(fields) => { + for (_, (meta, _)) in tasks.iter() { + if meta.contains(&fields) { + meta.cancel.cancel(); + } + } + } + Api::Validate(origin, target, fields, tx) => { + log_if_err(tx.send(register.validate(&origin, &target, &fields))); + } + Api::Shutdown(tx) => { + // Cancel / kill pending tasks + tasks.iter().for_each(|(_, (meta, handle))| { + meta.cancel.cancel(); + // TODO: we should wait for tasks will be cancelled by it self before abort. + handle.abort(); + }); + tasks.clear(); + log_if_err(tx.send(())); + break; + } + Api::InstalledPluginsList(tx) => { + // let plugs_ref_clone = Arc::clone(&plugins_manager); + log_if_err(tx.send(plugins::installed_plugins_list(&plugins_manager).await)) + } + Api::InvalidPluginsList(tx) => { + log_if_err(tx.send(plugins::invalid_plugins_list(&plugins_manager).await)) + } + Api::InstalledPluginsPaths(tx) => log_if_err( + tx.send(plugins::installed_plugins_paths(&plugins_manager).await), + ), + Api::InvalidPluginsPaths(tx) => { + log_if_err(tx.send(plugins::invalid_plugins_paths(&plugins_manager).await)) + } + Api::InstalledPluginInfo(path, tx) => log_if_err( + tx.send(plugins::installed_plugins_info(path, &plugins_manager).await), + ), + Api::InvalidPluginInfo(path, tx) => log_if_err( + tx.send(plugins::invalid_plugins_info(path, &plugins_manager).await), + ), + Api::PluginRunData(path, tx) => log_if_err( + tx.send(plugins::get_plugin_run_data(path, &plugins_manager).await), + ), + Api::ReloadPlugins(tx) => { + log_if_err(tx.send(plugins::reload_plugins(&plugins_manager).await)) + } + Api::AddPlugin(path, typ, tx) => { + log_if_err(tx.send(plugins::add_plugin(path, typ, &plugins_manager).await)) + } + Api::RemovePlugin(path, tx) => { + log_if_err(tx.send(plugins::remove_plugin(path, &plugins_manager).await)) + } + } + } + debug!("Session task is finished"); + }); + Ok((session, rx_callback_events)) + } + + /// Retrieves the configuration options for a list of components. + /// + /// This method sends an asynchronous request to obtain the settings of the specified components. + /// It uses the `Api::GetOptions` command to communicate with the underlying system. + /// + /// # Arguments + /// + /// * `targets` - A vector of UUIDs representing the components whose settings are being requested. + /// * `origin` - The source origin within which the components are being used. + /// + /// # Returns + /// + /// * `Ok(stypes::ComponentsOptionsList)` - The list of component configuration options. + /// * `Err(stypes::NativeError)` - An error if the request fails or the response is not received. + /// + /// # Errors + /// + /// The method returns an error if: + /// - The API request could not be sent. + /// - The response from the API could not be retrieved. + pub async fn get_options( + &self, + targets: Vec, + origin: stypes::SessionAction, + ) -> Result { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::GetOptions { + targets, + origin, + tx, + }), + "Fail to send Api::GetOptions", + )?; + response(rx.await, "Fail to get response from Api::GetOptions")? + } + + /// Requests the render supported by parser + /// + /// This API is used by the client to retrieve the render to use. + /// + /// # Arguments + /// + /// * `uuid` - Uuid of parser. + /// + /// # Result + /// + /// * `Result, NativeError>` - The result containing the render or an error. + /// + /// # Note + /// If component doesn't have render, returns `None` + pub async fn get_output_render( + &self, + uuid: Uuid, + ) -> Result, stypes::NativeError> { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::GetOutputRender(uuid, tx)), + "Fail to send Api::GetOutputRender", + )?; + response(rx.await, "Fail to get response from Api::GetOutputRender")? + } + + /// Requests the ident of component + /// + /// This API is used by the client to retrieve the identification of component parser or source. + /// + /// # Arguments + /// + /// * `Uuid` - Uuid of component (parser / source). + /// * `tx` - A one-shot sender used to deliver the result back to the client. + /// + /// # Result + /// + /// * `Result, NativeError>` - Ident of target component. + /// + /// # Note + /// If component doesn't exist, returns `None` + pub async fn get_ident( + &self, + uuid: Uuid, + ) -> Result, stypes::NativeError> { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::GetIdent(uuid, tx)), + "Fail to send Api::GetIdent", + )?; + response(rx.await, "Fail to get response from Api::GetIdent") + } + + /// Retrieves the list of available components of a specified type. + /// + /// This method sends an asynchronous request to get the list of components, filtered by type. + /// It uses the `Api::GetComponents` command to query the system. + /// + /// # Arguments + /// + /// * `origin` - The source origin within which the components are being used. + /// * `ty` - The type of components to retrieve (e.g., parser, source). + /// + /// # Returns + /// + /// * `Ok(Vec)` - A vector of component identifiers. + /// * `Err(stypes::NativeError)` - An error if the request fails or the response is not received. + /// + /// # Errors + /// + /// The method returns an error if: + /// - The API request could not be sent. + /// - The response from the API could not be retrieved. + pub async fn get_components( + &self, + origin: stypes::SessionAction, + ty: stypes::ComponentType, + ) -> Result, stypes::NativeError> { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::GetComponents(origin, ty, tx)), + "Fail to send Api::GetComponents", + )?; + response(rx.await, "Fail to get response from Api::GetComponents")? + } + + /// Asynchronously checks whether the specified source supports the Source Data Exchange (SDE) mechanism. + /// + /// This is a high-level wrapper that sends an [`Api::IsSdeSupported`] request to the internal API + /// and awaits the result. The method is asynchronous and returns either the result of the check + /// or an error if the request fails or the source is not found. + /// + /// # Arguments + /// + /// * `uuid` - The unique identifier of the source component to check. + /// * `origin` - The session context used to evaluate SDE permissions. + /// + /// # Returns + /// + /// * `Ok(true)` - if the source supports SDE. + /// * `Ok(false)` - if the source does not support SDE. + /// * `Err(NativeError)` - if the request could not be sent or the response failed. + pub async fn is_sde_supported( + &self, + uuid: Uuid, + origin: stypes::SessionAction, + ) -> Result { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::IsSdeSupported(origin, uuid, tx)), + "Fail to send Api::IsSdeSupported", + )?; + response(rx.await, "Fail to get response from Api::IsSdeSupported")? + } + + /// Returns a list of source and parser components that are compatible with the given session context + /// and can be used without explicit configuration. + /// + /// This method is designed to support "quick start" scenarios, where the user initiates a session + /// (e.g., opening a file) without manually selecting or configuring components. It filters available + /// sources and parsers based on the following criteria: + /// + /// - The component must declare compatibility with the provided [`SessionAction`]. + /// - The component must support default options (i.e., `get_default_options()` returns `Some`). + /// - The component's IO type must match the detected IO data type inferred from the session context + /// (e.g., plain text or raw binary). + /// + /// This method is typically used to automatically select a default setup when the user opens + /// a file (such as a text or DLT file), allowing immediate parsing and display without prompting + /// for configuration. + /// + /// # Arguments + /// + /// * `origin` - The session context (e.g., a file or set of files) used to infer compatibility and IO type. + /// + /// # Returns + /// + /// A [`ComponentsList`] containing identifiers of all matching sources and parsers. + /// If no fully compatible combination is found, an empty list is returned. + /// + /// # Errors + /// + /// Returns a [`NativeError`] if binary detection fails for one or more files. + /// + /// # Behavior + /// + /// - For a single file: detects whether the file is binary or plain text. + /// - For multiple files: only returns components if all are plain text, or all are binary. + /// Mixed types result in an empty list. + /// - For unsupported session types (e.g., `Source`, `ExportRaw`), returns an empty list. + pub async fn get_compatible_setup( + &self, + origin: stypes::SessionAction, + ) -> Result { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::GetCompatibleSetup(origin, tx)), + "Fail to send Api::GetCompatibleSetup", + )?; + response( + rx.await, + "Fail to get response from Api::GetCompatibleSetup", + )? + } + + /// Returns the default configuration options for the specified component, or an error if none are available. + /// + /// This method retrieves the default options for a parser or source component in the context of + /// the given [`SessionAction`]. It is used to determine whether the component can be instantiated + /// automatically, without requiring manual configuration by the user. + /// + /// Unlike earlier versions, this method no longer returns `Option`. If the component does not + /// support default options (i.e., it **must** be explicitly configured), the method returns a + /// [`NativeError`] indicating that it cannot be used in automatic setup scenarios. + /// + /// # Arguments + /// + /// * `origin` - The session context used to evaluate compatibility and scope. + /// * `target` - The UUID of the component for which default options are requested. + /// + /// # Returns + /// + /// * `Ok(FieldList)` - A list of fields to be used for instantiating the component. + /// If the list is empty, it means the component has no configurable options, but can still be used by default. + /// * `Err(NativeError)` - If the component: + /// - is not registered, + /// - does not support default options (and therefore cannot be used blindly). + /// + /// # Semantics + /// + /// - Empty `FieldList` (`FieldList(vec![])`) means the component has no settings but **is safe to use as default**. + /// - An error means the component **requires explicit configuration** and must not be auto-selected. + /// + /// # Errors + /// + /// Returns a [`NativeError`] with kind `Configuration` if: + /// - The component is not found, + /// - The component does not support default options in the given session context. + pub async fn get_default_options( + &self, + origin: stypes::SessionAction, + target: Uuid, + ) -> Result { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::GetDefaultOptions(origin, target, tx)), + "Fail to send Api::GetDefaultOptions", + )?; + response(rx.await, "Fail to get response from Api::GetDefaultOptions")? + } + /// Aborts the lazy loading tasks for the specified fields. + /// + /// This method sends a cancellation request for ongoing lazy loading tasks associated with the given field IDs. + /// It uses the `Api::CancelLoading` command to perform the operation. + /// + /// # Arguments + /// + /// * `fields` - A vector of field IDs for which the lazy loading should be aborted. + /// + /// # Returns + /// + /// * `Ok(())` - If the cancellation request was successfully sent. + /// * `Err(stypes::NativeError)` - An error if the API request could not be sent. + pub fn abort(&self, fields: Vec) -> Result<(), stypes::NativeError> { + send( + self.tx_api.send(Api::CancelLoading(fields)), + "Fail to send Api::CancelLoading", + ) + } + + /// Asynchronously validates the configuration of a specific component. + /// + /// This method sends a validation request to the system and waits for the response asynchronously. + /// + /// # Arguments + /// + /// * `source` - The origin type indicating the context in which the validation is performed. + /// * `target` - The identifier of the component (parser, source). + /// * `fields` - A list of configuration field values to be validated. + /// + /// # Returns + /// + /// `Result, stypes::NativeError>`: + /// * On success: A `HashMap` where the key is the field's identifier (`String`) and the value is the error message (`String`). + /// * If all fields are valid and have no errors, an empty `HashMap` is returned. + /// * On failure: A `stypes::NativeError` indicating the reason for the validation failure. + /// + /// # Errors + /// + /// Returns an error if: + /// * The API call to validate the configuration fails to send. + /// * The response from the validation API cannot be retrieved. + pub async fn validate( + &self, + source: stypes::SessionAction, + target: Uuid, + fields: Vec, + ) -> Result, stypes::NativeError> { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::Validate(source, target, fields, tx)), + "Fail to send Api::Validate", + )?; + response(rx.await, "Fail to get response from Api::Validate")? + } + + /// Initiates the shutdown process for the components session. + /// + /// This method sends a shutdown command to gracefully terminate the session. + /// It uses the `Api::Shutdown` command to initiate the shutdown. + /// + /// # Returns + /// + /// * `Ok(())` - If the shutdown request was successfully sent and acknowledged. + /// * `Err(stypes::NativeError)` - An error if the shutdown process fails. + /// + /// # Errors + /// + /// The method returns an error if: + /// - The API request could not be sent. + /// - The response from the API could not be retrieved. + pub async fn shutdown(&self) -> Result<(), stypes::NativeError> { + let (tx, rx): (oneshot::Sender<()>, oneshot::Receiver<()>) = oneshot::channel(); + send( + self.tx_api.send(Api::Shutdown(tx)), + "Fail to send Api::Shutdown", + )?; + response(rx.await, "Fail to get response from Api::Shutdown") + } + + /// Get all information of installed plugins . + pub async fn installed_plugins_list(&self) -> Result { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::InstalledPluginsList(tx)), + "Fail to send Api::InstalledPluginsList", + )?; + response( + rx.await, + "Fail to get response from Api::InstalledPluginsList", + )? + } + + /// Get all information of invalid plugins . + pub async fn invalid_plugins_list( + &self, + ) -> Result { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::InvalidPluginsList(tx)), + "Fail to send Api::InvalidPluginsList", + )?; + response( + rx.await, + "Fail to get response from Api::InvalidPluginsList", + )? + } + + /// Get the directory paths (considered ID) for installed plugins. + pub async fn installed_plugins_paths( + &self, + ) -> Result { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::InstalledPluginsPaths(tx)), + "Fail to send Api::InstalledPluginsPaths", + )?; + response( + rx.await, + "Fail to get response from Api::InstalledPluginsPaths", + )? + } + + /// Get the directory paths (considered ID) for invalid plugins. + pub async fn invalid_plugins_paths( + &self, + ) -> Result { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::InvalidPluginsPaths(tx)), + "Fail to send Api::InvalidPluginsPaths", + )?; + response( + rx.await, + "Fail to get response from Api::InvalidPluginsPaths", + )? + } + + /// Get all info for the installed plugin with provided directory path (considered ID) + pub async fn installed_plugin_info( + &self, + plugin_path: String, + ) -> Result, stypes::NativeError> { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::InstalledPluginInfo(plugin_path, tx)), + "Fail to send Api::InstalledPluginInfo", + )?; + response( + rx.await, + "Fail to get response from Api::InstalledPluginInfo", + )? + } + + /// Get all info for the invalid plugin with provided directory path (considered ID) + pub async fn invalid_plugin_info( + &self, + plugin_path: String, + ) -> Result, stypes::NativeError> { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::InvalidPluginInfo(plugin_path, tx)), + "Fail to send Api::InvalidPluginInfo", + )?; + response(rx.await, "Fail to get response from Api::InvalidPluginInfo")? + } + + /// Retrieves runtime data for a plugin located at the specified path. + pub async fn get_plugin_run_data( + &self, + plugin_path: String, + ) -> Result, stypes::NativeError> { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::PluginRunData(plugin_path, tx)), + "Fail to send Api::PluginRunData", + )?; + response(rx.await, "Fail to get response from Api::PluginRunData")? + } + + /// Reload the plugin directory. + pub async fn reload_plugins(&self) -> Result<(), stypes::NativeError> { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::ReloadPlugins(tx)), + "Fail to send Api::ReloadPlugins", + )?; + response(rx.await, "Fail to get response from Api::ReloadPlugins")? + //TODO AAZ: Reload components on reload plugins + } + + /// Adds a plugin with the given directory path and optional plugin type. + pub async fn add_plugin( + &self, + plugin_path: String, + plugin_type: Option, + ) -> Result<(), stypes::NativeError> { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api + .send(Api::AddPlugin(plugin_path, plugin_type, tx)), + "Fail to send Api::AddPlugin", + )?; + response(rx.await, "Fail to get response from Api::AddPlugin")? + } + + /// Removes the plugin with the given directory path. + pub async fn remove_plugin(&self, plugin_path: String) -> Result<(), stypes::NativeError> { + let (tx, rx) = oneshot::channel(); + send( + self.tx_api.send(Api::RemovePlugin(plugin_path, tx)), + "Fail to send Api::RemovePlugin", + )?; + response(rx.await, "Fail to get response from Api::RemovePlugin")? + } +} + +/// Logs an error if the given result contains an error. +/// +/// This function is a simple utility to reduce boilerplate when handling errors +/// that should be logged but do not require further processing. If the result is +/// an error, a log message will be recorded. +/// +/// # Arguments +/// +/// * `res` - A `Result` that may contain an error. If `Err`, it will be logged. +fn log_if_err(res: Result<(), E>) { + if res.is_err() { + error!("[Register] Fail to send response to Api"); + } +} + +/// Wraps the result of sending a message and converts a sending error into a native error. +/// +/// This function simplifies error handling when sending messages between asynchronous tasks. +/// It converts a `SendError` into a standardized `NativeError` format. +/// +/// # Arguments +/// +/// * `res` - A result from attempting to send a message via a channel. +/// * `msg` - A string slice providing context for the error message. +/// +/// # Returns +/// +/// * `Ok(())` - If the message was sent successfully. +/// * `Err(stypes::NativeError)` - If there was a failure during message sending. +fn send>( + res: Result<(), SendError>, + msg: S, +) -> Result<(), stypes::NativeError> { + res.map_err(|_| stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::ChannelError, + message: Some(msg.as_ref().to_string()), + }) +} + +/// Wraps the result of receiving a message and converts a receiving error into a native error. +/// +/// This function simplifies error handling when awaiting a response from an asynchronous channel. +/// It converts a `RecvError` into a standardized `NativeError` format with a detailed message. +/// +/// # Arguments +/// +/// * `res` - A result from awaiting a response from a channel. +/// * `msg` - A string slice providing context for the error message. +/// +/// # Returns +/// +/// * `Ok(T)` - The successfully received value. +/// * `Err(stypes::NativeError)` - If there was a failure during message reception. +fn response>(res: Result, msg: S) -> Result { + res.map_err(|e| stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::ChannelError, + message: Some(format!("{}: {e:?}", msg.as_ref())), + }) +} + +// use parsers::api::*; +// use sources::api::*; + +pub fn parsers_registration(register: &mut Register) -> Result<(), stypes::NativeError> { + register.add_parser(parsers::dlt::descriptor::Descriptor::default())?; + register.add_parser(parsers::dlt::raw::descriptor::Descriptor::default())?; + register.add_parser(parsers::someip::descriptor::Descriptor::default())?; + register.add_parser(parsers::text::descriptor::Descriptor::default())?; + Ok(()) +} + +pub fn sources_registration(register: &mut Register) -> Result<(), stypes::NativeError> { + register.add_source(sources::binary::raw::Descriptor::default())?; + register.add_source(sources::binary::pcap::legacy::Descriptor::default())?; + register.add_source(sources::binary::pcap::ng::Descriptor::default())?; + register.add_source(sources::socket::tcp::Descriptor::default())?; + register.add_source(sources::socket::udp::Descriptor::default())?; + register.add_source(sources::serial::descriptor::Descriptor::default())?; + register.add_source(sources::command::Descriptor::default())?; + Ok(()) +} diff --git a/application/apps/indexer/session/src/unbound/commands/plugins.rs b/application/apps/indexer/session/src/register/plugins.rs similarity index 65% rename from application/apps/indexer/session/src/unbound/commands/plugins.rs rename to application/apps/indexer/session/src/register/plugins.rs index 9ff8a896d..0cb58bc2f 100644 --- a/application/apps/indexer/session/src/unbound/commands/plugins.rs +++ b/application/apps/indexer/session/src/register/plugins.rs @@ -1,53 +1,47 @@ use std::path::PathBuf; -use crate::unbound::signal::Signal; use plugins_host::plugins_manager::PluginsManager; use stypes::{ - CommandOutcome, ComputationError, InvalidPluginEntity, InvalidPluginsList, PluginEntity, - PluginRunData, PluginType, PluginsList, PluginsPathsList, + InvalidPluginEntity, InvalidPluginsList, NativeError, PluginEntity, PluginRunData, PluginType, + PluginsList, PluginsPathsList, }; use tokio::sync::RwLock; /// Initialize the plugin manager loading all the plugins from their directory. -pub async fn load_manager() -> Result { - PluginsManager::load() - .await - .map_err(|err| ComputationError::NativeError(err.into())) +pub async fn load_manager() -> Result { + PluginsManager::load().await.map_err(|err| err.into()) } /// Get all information of installed plugins . pub async fn installed_plugins_list( plugins_manager: &RwLock, - _signal: Signal, -) -> Result, ComputationError> { +) -> Result { let manager = plugins_manager.read().await; let installed_plugins = manager.installed_plugins().cloned().collect(); let plugins = PluginsList(installed_plugins); - Ok(CommandOutcome::Finished(plugins)) + Ok(plugins) } /// Get all information of invalid plugins . pub async fn invalid_plugins_list( plugins_manager: &RwLock, - _signal: Signal, -) -> Result, ComputationError> { +) -> Result { let manager = plugins_manager.read().await; let invalid_plugins = manager.invalid_plugins().cloned().collect(); let plugins = InvalidPluginsList(invalid_plugins); - Ok(CommandOutcome::Finished(plugins)) + Ok(plugins) } /// Get the directory paths (considered ID) of installed plugins . pub async fn installed_plugins_paths( plugins_manager: &RwLock, - _signal: Signal, -) -> Result, ComputationError> { +) -> Result { let manager = plugins_manager.read().await; let installed_paths: Vec<_> = manager @@ -57,14 +51,13 @@ pub async fn installed_plugins_paths( let plugins = PluginsPathsList(installed_paths); - Ok(CommandOutcome::Finished(plugins)) + Ok(plugins) } /// Get the directory paths (considered ID) of invalid plugins . pub async fn invalid_plugins_paths( plugins_manager: &RwLock, - _signal: Signal, -) -> Result, ComputationError> { +) -> Result { let manager = plugins_manager.read().await; let invalid_paths: Vec<_> = manager @@ -74,37 +67,35 @@ pub async fn invalid_plugins_paths( let plugins = PluginsPathsList(invalid_paths); - Ok(CommandOutcome::Finished(plugins)) + Ok(plugins) } /// Get all info for the installed plugin with provided directory path (considered ID) pub async fn installed_plugins_info( plugin_path: String, plugins_manager: &RwLock, - _signal: Signal, -) -> Result>, ComputationError> { +) -> Result, NativeError> { let manager = plugins_manager.read().await; let plugin = manager .get_installed_plugin(&PathBuf::from(plugin_path)) .cloned(); - Ok(CommandOutcome::Finished(plugin)) + Ok(plugin) } /// Get all info for the invalid plugin with provided directory path (considered ID) pub async fn invalid_plugins_info( plugin_path: String, plugins_manager: &RwLock, - _signal: Signal, -) -> Result>, ComputationError> { +) -> Result, NativeError> { let manager = plugins_manager.read().await; let invalid_plug = manager .get_invalid_plugin(&PathBuf::from(plugin_path)) .cloned(); - Ok(CommandOutcome::Finished(invalid_plug)) + Ok(invalid_plug) } /// Retrieves runtime data for a plugin located at the specified path. @@ -121,30 +112,23 @@ pub async fn invalid_plugins_info( pub async fn get_plugin_run_data( plugin_path: String, plugins_manager: &RwLock, - _signal: Signal, -) -> Result>, ComputationError> { +) -> Result, NativeError> { let manager = plugins_manager.read().await; let invalid_plug = manager .get_plugin_run_data(PathBuf::from(plugin_path)) .cloned(); - Ok(CommandOutcome::Finished(invalid_plug)) + Ok(invalid_plug) } /// Reload plugins from the plugins directory. -pub async fn reload_plugins( - plugins_manager: &RwLock, - _signal: Signal, -) -> Result, ComputationError> { +pub async fn reload_plugins(plugins_manager: &RwLock) -> Result<(), NativeError> { let mut manager = plugins_manager.write().await; - manager - .reload() - .await - .map_err(|err| ComputationError::NativeError(err.into()))?; + manager.reload().await?; - Ok(CommandOutcome::Finished(())) + Ok(()) } /// Adds a plugin with the given directory path and the optional plugin type. @@ -156,16 +140,12 @@ pub async fn add_plugin( plugin_path: String, plugin_type: Option, plugins_manager: &RwLock, - _signal: Signal, -) -> Result, ComputationError> { +) -> Result<(), NativeError> { let mut manager = plugins_manager.write().await; - manager - .add_plugin(plugin_path.into(), plugin_type) - .await - .map_err(|err| ComputationError::NativeError(err.into()))?; + manager.add_plugin(plugin_path.into(), plugin_type).await?; - Ok(CommandOutcome::Finished(())) + Ok(()) } /// Removes the plugin with the given directory path. @@ -174,14 +154,12 @@ pub async fn add_plugin( pub async fn remove_plugin( plugin_path: String, plugins_manager: &RwLock, - _signal: Signal, -) -> Result, ComputationError> { +) -> Result<(), NativeError> { let mut manager = plugins_manager.write().await; manager .remove_plugin(PathBuf::from(plugin_path).as_path()) - .await - .map_err(|err| ComputationError::NativeError(err.into()))?; + .await?; - Ok(CommandOutcome::Finished(())) + Ok(()) } diff --git a/application/apps/indexer/session/src/session.rs b/application/apps/indexer/session/src/session.rs index 1e14be443..5a3d94b97 100644 --- a/application/apps/indexer/session/src/session.rs +++ b/application/apps/indexer/session/src/session.rs @@ -1,15 +1,14 @@ use crate::{ - operations, - operations::Operation, - state, - state::{IndexesMode, SessionStateAPI}, - tracker, - tracker::OperationTrackerAPI, + operations::{self, Operation}, + register::{parsers_registration, sources_registration}, + state::{self, IndexesMode, SessionStateAPI}, + tracker::{self, OperationTrackerAPI}, }; use futures::Future; use log::{debug, error, warn}; use processor::{grabber::LineRange, search::filter::SearchFilter}; -use std::{ops::RangeInclusive, path::PathBuf}; +use register::Register; +use std::{ops::RangeInclusive, path::PathBuf, sync::Arc}; use tokio::{ join, sync::{ @@ -31,6 +30,7 @@ pub struct Session { tx_operations: UnboundedSender, destroyed: CancellationToken, destroying: CancellationToken, + pub register: Arc, pub state: SessionStateAPI, pub tracker: OperationTrackerAPI, } @@ -54,11 +54,18 @@ impl Session { UnboundedSender, UnboundedReceiver, ) = unbounded_channel(); + let mut register: Register = Register::new(); + // Registre parsers + parsers_registration(&mut register).map_err(stypes::ComputationError::NativeError)?; + // Registre sources + sources_registration(&mut register).map_err(stypes::ComputationError::NativeError)?; + // Create a session let session = Self { uuid, tx_operations: tx_operations.clone(), destroyed: CancellationToken::new(), destroying: CancellationToken::new(), + register: Arc::new(register), state: state_api.clone(), tracker: tracker_api.clone(), }; @@ -376,12 +383,12 @@ impl Session { pub fn observe( &self, operation_id: Uuid, - options: stypes::ObserveOptions, + options: stypes::SessionSetup, ) -> Result<(), stypes::ComputationError> { self.tx_operations .send(Operation::new( operation_id, - operations::OperationKind::Observe(options), + operations::OperationKind::Observe(options, self.register.clone()), )) .map_err(|e| stypes::ComputationError::Communication(e.to_string())) } @@ -437,16 +444,18 @@ impl Session { pub fn export_raw( &self, - operation_id: Uuid, - out_path: PathBuf, - ranges: Vec>, + _operation_id: Uuid, + _out_path: PathBuf, + _ranges: Vec>, ) -> Result<(), stypes::ComputationError> { - self.tx_operations - .send(Operation::new( - operation_id, - operations::OperationKind::ExportRaw { out_path, ranges }, - )) - .map_err(|e| stypes::ComputationError::Communication(e.to_string())) + // We should not ask client for stypes::SessionSetup and we should take it from state + todo!("Not implemented") + // self.tx_operations + // .send(Operation::new( + // operation_id, + // operations::OperationKind::ExportRaw { out_path, ranges }, + // )) + // .map_err(|e| stypes::ComputationError::Communication(e.to_string())) } pub async fn is_raw_export_available(&self) -> Result { diff --git a/application/apps/indexer/session/src/state/api.rs b/application/apps/indexer/session/src/state/api.rs index a67f4af3e..08ff3f6fb 100644 --- a/application/apps/indexer/session/src/state/api.rs +++ b/application/apps/indexer/session/src/state/api.rs @@ -7,7 +7,6 @@ use crate::{ tracker::OperationTrackerAPI, }; use log::error; -use parsers; use processor::{ grabber::LineRange, map::{FiltersStats, ScaledDistribution}, @@ -17,7 +16,7 @@ use processor::{ }, }; use std::{collections::HashMap, fmt::Display, ops::RangeInclusive, path::PathBuf}; -use stypes::GrabbedElement; +use stypes::{GrabbedElement, SessionDescriptor}; use tokio::sync::{ mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}, oneshot, @@ -43,8 +42,7 @@ pub enum Api { FlushSessionFile(oneshot::Sender>), GetSessionFileOrigin(oneshot::Sender, stypes::NativeError>>), UpdateSession((u16, oneshot::Sender>)), - AddSource((String, oneshot::Sender)), - GetSource((String, oneshot::Sender>)), + AddSource(Uuid, SessionDescriptor, oneshot::Sender), GetSourcesDefinitions(oneshot::Sender>), #[allow(clippy::large_enum_variant)] AddExecutedObserve((stypes::ObserveOptions, oneshot::Sender<()>)), @@ -193,7 +191,7 @@ pub enum Api { SetDebugMode((bool, oneshot::Sender<()>)), NotifyCancelingOperation(Uuid), NotifyCanceledOperation(Uuid), - AddAttachment(parsers::Attachment), + AddAttachment(parsers::api::Attachment), GetAttachments(oneshot::Sender>), // Used for tests of error handeling ShutdownWithError, @@ -212,8 +210,7 @@ impl Display for Api { Self::FlushSessionFile(_) => "FlushSessionFile", Self::GetSessionFileOrigin(_) => "GetSessionFileOrigin", Self::UpdateSession(_) => "UpdateSession", - Self::AddSource(_) => "AddSource", - Self::GetSource(_) => "GetSource", + Self::AddSource(..) => "AddSource", Self::GetSourcesDefinitions(_) => "GetSourcesDefinitions", Self::AddExecutedObserve(_) => "AddExecutedObserve", Self::GetExecutedHolder(_) => "GetExecutedHolder", @@ -466,15 +463,13 @@ impl SessionStateAPI { .await? } - pub async fn add_source(&self, uuid: &str) -> Result { - let (tx, rx) = oneshot::channel(); - self.exec_operation(Api::AddSource((uuid.to_owned(), tx)), rx) - .await - } - - pub async fn get_source(&self, uuid: &str) -> Result, stypes::NativeError> { + pub async fn add_source( + &self, + operation_uuid: Uuid, + desciptor: SessionDescriptor, + ) -> Result { let (tx, rx) = oneshot::channel(); - self.exec_operation(Api::GetSource((uuid.to_owned(), tx)), rx) + self.exec_operation(Api::AddSource(operation_uuid, desciptor, tx), rx) .await } @@ -688,7 +683,10 @@ impl SessionStateAPI { }) } - pub fn add_attachment(&self, origin: parsers::Attachment) -> Result<(), stypes::NativeError> { + pub fn add_attachment( + &self, + origin: parsers::api::Attachment, + ) -> Result<(), stypes::NativeError> { self.tx_api.send(Api::AddAttachment(origin)).map_err(|e| { stypes::NativeError::channel( &format!("fail to send to Api::AddAttachment; error: {e}",), diff --git a/application/apps/indexer/session/src/state/attachments.rs b/application/apps/indexer/session/src/state/attachments.rs index 28872426b..6ef1a1655 100644 --- a/application/apps/indexer/session/src/state/attachments.rs +++ b/application/apps/indexer/session/src/state/attachments.rs @@ -1,5 +1,4 @@ use mime_guess; -use parsers::{self}; use std::{ collections::HashMap, fs::{File, create_dir}, @@ -100,7 +99,7 @@ impl Attachments { } pub fn get_attch_from( - origin: parsers::Attachment, + origin: parsers::api::Attachment, store_folder: &PathBuf, ) -> Result { if !store_folder.exists() { @@ -147,7 +146,7 @@ impl Attachments { pub fn add( &mut self, - attachment: parsers::Attachment, + attachment: parsers::api::Attachment, ) -> Result { if let Some(dest) = self.dest.as_ref() { let uuid = Uuid::new_v4(); diff --git a/application/apps/indexer/session/src/state/mod.rs b/application/apps/indexer/session/src/state/mod.rs index e329eda2f..fd297e9d9 100644 --- a/application/apps/indexer/session/src/state/mod.rs +++ b/application/apps/indexer/session/src/state/mod.rs @@ -1,5 +1,4 @@ use log::{debug, error}; -use parsers; use processor::{ grabber::LineRange, map::SearchMap, @@ -496,7 +495,7 @@ impl SessionState { fn handle_add_attachment( &mut self, - origin: parsers::Attachment, + origin: parsers::api::Attachment, tx_callback_events: UnboundedSender, ) -> Result<(), stypes::NativeError> { let attachment = self.attachments.add(origin)?; @@ -583,19 +582,21 @@ pub async fn run( stypes::NativeError::channel("Failed to respond to Api::UpdateSession") })?; } - Api::AddSource((uuid, tx_response)) => { + Api::AddSource(uuid, descriptor, tx_response) => { tx_response - .send(state.session_file.sources.add_source(uuid)) - .map_err(|_| { - stypes::NativeError::channel("Failed to respond to Api::AddSource") - })?; - } - Api::GetSource((uuid, tx_response)) => { - tx_response - .send(state.session_file.sources.get_source(uuid)) + .send( + state + .session_file + .sources + .add_source(uuid, descriptor.clone()), + ) .map_err(|_| { stypes::NativeError::channel("Failed to respond to Api::AddSource") })?; + tx_callback_events.send(stypes::CallbackEvent::SessionDescriptor { + uuid, + desc: descriptor, + })?; } Api::GetSourcesDefinitions(tx_response) => { tx_response diff --git a/application/apps/indexer/session/src/state/source_ids.rs b/application/apps/indexer/session/src/state/source_ids.rs index 3eaeaf410..d08f11e0c 100644 --- a/application/apps/indexer/session/src/state/source_ids.rs +++ b/application/apps/indexer/session/src/state/source_ids.rs @@ -1,5 +1,8 @@ use std::{collections::HashMap, ops::RangeInclusive}; +use stypes::SessionDescriptor; +use uuid::Uuid; + pub struct MappedRanges<'a> { ranges: Vec<&'a (RangeInclusive, u16)>, } @@ -22,7 +25,7 @@ impl<'a> MappedRanges<'a> { #[derive(Debug)] pub struct SourceIDs { - pub sources: HashMap, + pub sources: HashMap, pub map: Vec<(RangeInclusive, u16)>, pub recent: Option, } @@ -36,18 +39,12 @@ impl SourceIDs { } } - pub fn add_source(&mut self, alias: String) -> u16 { + pub fn add_source(&mut self, uuid: Uuid, descriptor: SessionDescriptor) -> u16 { let key = self.sources.len() as u16; - self.sources.insert(key, alias); + self.sources.insert(key, (uuid, descriptor)); key } - pub fn get_source(&mut self, alias: String) -> Option { - self.sources - .iter() - .find_map(|(key, val)| if val == &alias { Some(*key) } else { None }) - } - pub fn is_source_same(&self, source_id: u16) -> bool { if let Some(id) = self.recent { id == source_id @@ -78,9 +75,10 @@ impl SourceIDs { pub fn get_sources_definitions(&self) -> Vec { self.sources .iter() - .map(|(id, alias)| stypes::SourceDefinition { + .map(|(id, (uuid, descriptor))| stypes::SourceDefinition { id: *id, - alias: alias.to_string(), + uuid: *uuid, + descriptor: descriptor.clone(), }) .collect::>() } diff --git a/application/apps/indexer/session/src/tracker.rs b/application/apps/indexer/session/src/tracker.rs index 450a82ce0..f68b78c6b 100644 --- a/application/apps/indexer/session/src/tracker.rs +++ b/application/apps/indexer/session/src/tracker.rs @@ -1,6 +1,6 @@ use crate::{operations::OperationStat, progress::ProgressProviderAPI, state::SessionStateAPI}; use log::{debug, error}; -use sources::sde::SdeSender; +use processor::producer::sde::SdeSender; use std::collections::{HashMap, hash_map::Entry}; use tokio::{ sync::{ diff --git a/application/apps/indexer/session/src/unbound/api.rs b/application/apps/indexer/session/src/unbound/api.rs index 4b51efacc..dc646d8a1 100644 --- a/application/apps/indexer/session/src/unbound/api.rs +++ b/application/apps/indexer/session/src/unbound/api.rs @@ -209,133 +209,4 @@ impl UnboundSessionAPI { self.process_command(id, rx_results, Command::Sleep(ms, tx_results)) .await } - - /// Get all information of installed plugins . - pub async fn installed_plugins_list( - &self, - id: u64, - ) -> Result, stypes::ComputationError> { - let (tx_results, rx_results) = oneshot::channel(); - self.process_command(id, rx_results, Command::InstalledPluginsList(tx_results)) - .await - } - - /// Get all information of invalid plugins . - pub async fn invalid_plugins_list( - &self, - id: u64, - ) -> Result, stypes::ComputationError> { - let (tx_results, rx_results) = oneshot::channel(); - self.process_command(id, rx_results, Command::InvalidPluginsList(tx_results)) - .await - } - - /// Get the directory paths (considered ID) for installed plugins. - pub async fn installed_plugins_paths( - &self, - id: u64, - ) -> Result, stypes::ComputationError> { - let (tx_results, rx_results) = oneshot::channel(); - self.process_command(id, rx_results, Command::InstalledPluginsPaths(tx_results)) - .await - } - - /// Get the directory paths (considered ID) for invalid plugins. - pub async fn invalid_plugins_paths( - &self, - id: u64, - ) -> Result, stypes::ComputationError> { - let (tx_results, rx_results) = oneshot::channel(); - self.process_command(id, rx_results, Command::InvalidPluginsPaths(tx_results)) - .await - } - - /// Get all info for the installed plugin with provided directory path (considered ID) - pub async fn installed_plugin_info( - &self, - id: u64, - plugin_path: String, - ) -> Result>, stypes::ComputationError> - { - let (tx_results, rx_results) = oneshot::channel(); - self.process_command( - id, - rx_results, - Command::InstalledPluginInfo(plugin_path, tx_results), - ) - .await - } - - /// Get all info for the invalid plugin with provided directory path (considered ID) - pub async fn invalid_plugin_info( - &self, - id: u64, - plugin_path: String, - ) -> Result>, stypes::ComputationError> - { - let (tx_results, rx_results) = oneshot::channel(); - self.process_command( - id, - rx_results, - Command::InvalidPluginInfo(plugin_path, tx_results), - ) - .await - } - - /// Retrieves runtime data for a plugin located at the specified path. - pub async fn get_plugin_run_data( - &self, - id: u64, - plugin_path: String, - ) -> Result>, stypes::ComputationError> - { - let (tx_results, rx_results) = oneshot::channel(); - self.process_command( - id, - rx_results, - Command::PluginRunData(plugin_path, tx_results), - ) - .await - } - - /// Reload the plugin directory. - pub async fn reload_plugins( - &self, - id: u64, - ) -> Result, stypes::ComputationError> { - let (tx_results, rx_results) = oneshot::channel(); - self.process_command(id, rx_results, Command::ReloadPlugins(tx_results)) - .await - } - - /// Adds a plugin with the given directory path and optional plugin type. - pub async fn add_plugin( - &self, - id: u64, - plugin_path: String, - plugin_type: Option, - ) -> Result, stypes::ComputationError> { - let (tx_results, rx_results) = oneshot::channel(); - self.process_command( - id, - rx_results, - Command::AddPlugin(plugin_path, plugin_type, tx_results), - ) - .await - } - - /// Removes the plugin with the given directory path. - pub async fn remove_plugin( - &self, - id: u64, - plugin_path: String, - ) -> Result, stypes::ComputationError> { - let (tx_results, rx_results) = oneshot::channel(); - self.process_command( - id, - rx_results, - Command::RemovePlugin(plugin_path, tx_results), - ) - .await - } } diff --git a/application/apps/indexer/session/src/unbound/commands/mod.rs b/application/apps/indexer/session/src/unbound/commands/mod.rs index fc4762b79..403f73e52 100644 --- a/application/apps/indexer/session/src/unbound/commands/mod.rs +++ b/application/apps/indexer/session/src/unbound/commands/mod.rs @@ -3,7 +3,6 @@ mod checksum; mod dlt; mod file; mod folder; -pub mod plugins; mod process; mod regex; mod serial; @@ -12,8 +11,7 @@ mod sleep; mod someip; use crate::unbound::commands::someip::get_someip_statistic; -use plugins_host::plugins_manager::PluginsManager; -use tokio::sync::{RwLock, oneshot}; +use tokio::sync::oneshot; use super::signal::Signal; use log::{debug, error}; @@ -83,67 +81,6 @@ pub enum Command { i64, oneshot::Sender, stypes::ComputationError>>, ), - /// Get all information of the installed plugins . - InstalledPluginsList( - oneshot::Sender< - Result, stypes::ComputationError>, - >, - ), - /// Get all information of invalid plugins . - InvalidPluginsList( - oneshot::Sender< - Result, stypes::ComputationError>, - >, - ), - /// Get the directory paths (considered ID) for installed plugins. - InstalledPluginsPaths( - oneshot::Sender< - Result, stypes::ComputationError>, - >, - ), - /// Get the directory paths (considered ID) for invalid plugins. - InvalidPluginsPaths( - oneshot::Sender< - Result, stypes::ComputationError>, - >, - ), - /// Get all info for the installed plugin with provided directory path (considered ID) - InstalledPluginInfo( - String, - oneshot::Sender< - Result>, stypes::ComputationError>, - >, - ), - /// Get all info for the invalid plugin with provided directory path (considered ID) - InvalidPluginInfo( - String, - oneshot::Sender< - Result< - stypes::CommandOutcome>, - stypes::ComputationError, - >, - >, - ), - /// Retrieves runtime data for a plugin located at the specified path. - PluginRunData( - String, - oneshot::Sender< - Result>, stypes::ComputationError>, - >, - ), - /// Reload all the plugins from their directory. - ReloadPlugins(oneshot::Sender, stypes::ComputationError>>), - /// Adds a plugin with the given directory path and optional plugin type. - AddPlugin( - String, - Option, - oneshot::Sender, stypes::ComputationError>>, - ), - /// Removes the plugin with the given directory path. - RemovePlugin( - String, - oneshot::Sender, stypes::ComputationError>>, - ), } impl std::fmt::Display for Command { @@ -164,22 +101,12 @@ impl std::fmt::Display for Command { Command::GetSomeipStatistic(_, _) => "Getting someip statistic", Command::GetRegexError(_, _) => "Checking regex", Command::IsFileBinary(_, _) => "Checking if file is binary", - Command::InstalledPluginsList(..) => "Getting installed plugins", - Command::InvalidPluginsList(..) => "Getting invalid plugins", - Command::InstalledPluginsPaths(..) => "Getting installed plugins paths", - Command::InvalidPluginsPaths(..) => "Getting invaild plugins paths", - Command::InstalledPluginInfo(..) => "Getting installed plugin info", - Command::InvalidPluginInfo(..) => "Getting invalid plugin info", - Command::PluginRunData(..) => "Getting plugin run data", - Command::ReloadPlugins(..) => "Reloading plugins' information", - Command::AddPlugin(..) => "Adding plugin", - Command::RemovePlugin(..) => "Removing plugin", } ) } } -pub async fn process(command: Command, signal: Signal, plugins_manager: &RwLock) { +pub async fn process(command: Command, signal: Signal) { let cmd = command.to_string(); debug!("Processing command: {cmd}"); if match command { @@ -212,36 +139,6 @@ pub async fn process(command: Command, signal: Signal, plugins_manager: &RwLock< Command::CancelTest(a, b, tx) => tx .send(cancel_test::cancel_test(a, b, signal).await) .is_err(), - Command::InstalledPluginsList(tx) => tx - .send(plugins::installed_plugins_list(plugins_manager, signal).await) - .is_err(), - Command::InvalidPluginsList(tx) => tx - .send(plugins::invalid_plugins_list(plugins_manager, signal).await) - .is_err(), - Command::InstalledPluginsPaths(tx) => tx - .send(plugins::installed_plugins_paths(plugins_manager, signal).await) - .is_err(), - Command::InvalidPluginsPaths(tx) => tx - .send(plugins::invalid_plugins_paths(plugins_manager, signal).await) - .is_err(), - Command::InstalledPluginInfo(path, tx) => tx - .send(plugins::installed_plugins_info(path, plugins_manager, signal).await) - .is_err(), - Command::InvalidPluginInfo(path, tx) => tx - .send(plugins::invalid_plugins_info(path, plugins_manager, signal).await) - .is_err(), - Command::PluginRunData(path, tx) => tx - .send(plugins::get_plugin_run_data(path, plugins_manager, signal).await) - .is_err(), - Command::ReloadPlugins(tx) => tx - .send(plugins::reload_plugins(plugins_manager, signal).await) - .is_err(), - Command::AddPlugin(path, typ, tx) => tx - .send(plugins::add_plugin(path, typ, plugins_manager, signal).await) - .is_err(), - Command::RemovePlugin(path, tx) => tx - .send(plugins::remove_plugin(path, plugins_manager, signal).await) - .is_err(), } { error!("Fail to send response for command: {cmd}"); } @@ -262,16 +159,6 @@ pub fn err(command: Command, err: stypes::ComputationError) { Command::SerialPortsList(tx) => tx.send(Err(err)).is_err(), Command::IsFileBinary(_filepath, tx) => tx.send(Err(err)).is_err(), Command::CancelTest(_a, _b, tx) => tx.send(Err(err)).is_err(), - Command::InstalledPluginsList(tx) => tx.send(Err(err)).is_err(), - Command::InvalidPluginsList(tx) => tx.send(Err(err)).is_err(), - Command::InstalledPluginsPaths(tx) => tx.send(Err(err)).is_err(), - Command::InvalidPluginsPaths(tx) => tx.send(Err(err)).is_err(), - Command::InstalledPluginInfo(_, tx) => tx.send(Err(err)).is_err(), - Command::InvalidPluginInfo(_, tx) => tx.send(Err(err)).is_err(), - Command::PluginRunData(_, tx) => tx.send(Err(err)).is_err(), - Command::ReloadPlugins(tx) => tx.send(Err(err)).is_err(), - Command::AddPlugin(_, _, tx) => tx.send(Err(err)).is_err(), - Command::RemovePlugin(_, tx) => tx.send(Err(err)).is_err(), } { error!("Fail to send error response for command: {cmd}"); } diff --git a/application/apps/indexer/session/src/unbound/mod.rs b/application/apps/indexer/session/src/unbound/mod.rs index 2471e5135..409fce379 100644 --- a/application/apps/indexer/session/src/unbound/mod.rs +++ b/application/apps/indexer/session/src/unbound/mod.rs @@ -12,12 +12,9 @@ use crate::{ }; use cleanup::cleanup_temp_dir; use log::{debug, error, warn}; -use std::{collections::HashMap, sync::Arc}; +use std::collections::HashMap; use tokio::{ - sync::{ - RwLock, - mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}, - }, + sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}, time::{Duration, timeout}, }; use tokio_util::sync::CancellationToken; @@ -46,13 +43,6 @@ impl UnboundSession { } pub async fn init(&mut self) -> Result<(), stypes::ComputationError> { - // TODO: Plugins manager is used temporally here in initial phase and we should consider - // moving it to its own module. Reasons: - // * It doesn't need parallelism for most of task. - // * It'll need different state and locking management for downloading plugins, Updating - // caches etc... - let plugins_manager = commands::plugins::load_manager().await?; - let plugins_manager = Arc::new(RwLock::new(plugins_manager)); let finished = self.finished.clone(); let mut rx = self .rx @@ -86,10 +76,9 @@ impl UnboundSession { jobs.insert(id, signal.clone()); UnboundSession::started(&progress, job.to_string(), &mut uuids, &id); let api = session_api.clone(); - let plugs_ref_clone = Arc::clone(&plugins_manager); tokio::spawn(async move { debug!("Job {job} has been called"); - commands::process(job, signal.clone(), plugs_ref_clone.as_ref()).await; + commands::process(job, signal.clone()).await; signal.confirm(); let _ = api.remove_command(id); }); diff --git a/application/apps/indexer/session/tests/snapshot_tests/mod.rs b/application/apps/indexer/session/tests/snapshot_tests/mod.rs index f14b98fb0..dd19cda89 100644 --- a/application/apps/indexer/session/tests/snapshot_tests/mod.rs +++ b/application/apps/indexer/session/tests/snapshot_tests/mod.rs @@ -18,24 +18,25 @@ mod utls; use std::path::PathBuf; +use stypes::{SessionAction, SessionSetup}; use utls::*; #[tokio::test] async fn observe_dlt_session() { - let input = "../../../developing/resources/attachments.dlt"; - let parser_settings = stypes::DltParserSettings::default(); - let session_main_file = run_observe_session( - input, - stypes::FileFormat::Binary, - stypes::ParserType::Dlt(parser_settings.clone()), - ) - .await; + let setup = SessionSetup { + origin: SessionAction::File(PathBuf::from( + "../../../developing/resources/attachments.dlt", + )), + parser: parsers::dlt::descriptor::get_default_options(None, None), + source: sources::binary::raw::get_default_options(), + }; + let session_main_file = run_observe_session(setup.clone()).await; let session_files = SessionFiles::from_session_file(&session_main_file); insta::with_settings!({ description => "Snapshot for DLT file with text attachments.", - info => &parser_settings, + info => &setup, omit_expression => true, prepend_module_to_snapshot => false, }, { @@ -45,31 +46,23 @@ async fn observe_dlt_session() { #[tokio::test] async fn observe_dlt_with_someip_session() { - let input = "../../../developing/resources/someip.dlt"; - let fibex_file = "../../../developing/resources/someip.xml"; - - assert!( - PathBuf::from(fibex_file).exists(), - "Fibex file path doesn't exist. Path: {fibex_file}" - ); - - let parser_settings = stypes::DltParserSettings { - fibex_file_paths: Some(vec![String::from(fibex_file)]), - ..Default::default() + let setup = SessionSetup { + origin: SessionAction::File(PathBuf::from("../../../developing/resources/someip.dlt")), + parser: parsers::dlt::descriptor::get_default_options( + Some(vec![PathBuf::from( + "../../../developing/resources/someip.xml", + )]), + None, + ), + source: sources::binary::raw::get_default_options(), }; - - let session_main_file = run_observe_session( - input, - stypes::FileFormat::Binary, - stypes::ParserType::Dlt(parser_settings.clone()), - ) - .await; + let session_main_file = run_observe_session(setup.clone()).await; let session_files = SessionFiles::from_session_file(&session_main_file); insta::with_settings!({ description => "Snapshot for DLT file with SomeIP network trace.", - info => &parser_settings, + info => &setup, omit_expression => true, prepend_module_to_snapshot => false, }, { @@ -79,30 +72,21 @@ async fn observe_dlt_with_someip_session() { #[tokio::test] async fn observe_someip_bcapng_session() { - let input = "../../../developing/resources/someip.pcapng"; - let fibex_file = "../../../developing/resources/someip.xml"; - - assert!( - PathBuf::from(fibex_file).exists(), - "Fibex file path doesn't exist. Path: {fibex_file}" - ); - - let parser_settings = stypes::SomeIpParserSettings { - fibex_file_paths: Some(vec![String::from(fibex_file)]), + let setup = SessionSetup { + origin: SessionAction::File(PathBuf::from("../../../developing/resources/someip.pcapng")), + parser: parsers::someip::descriptor::get_default_options(Some(vec![PathBuf::from( + "../../../developing/resources/someip.xml", + )])), + source: sources::binary::pcap::ng::get_default_options(), }; - let session_main_file = run_observe_session( - input, - stypes::FileFormat::PcapNG, - stypes::ParserType::SomeIp(parser_settings.clone()), - ) - .await; + let session_main_file = run_observe_session(setup.clone()).await; let session_files = SessionFiles::from_session_file(&session_main_file); insta::with_settings!({ description => "Snapshot for SomeIP file with Pcapng byte source.", - info => &parser_settings, + info => &setup, omit_expression => true, prepend_module_to_snapshot => false, }, { @@ -112,30 +96,21 @@ async fn observe_someip_bcapng_session() { #[tokio::test] async fn observe_someip_legacy_session() { - let input = "../../../developing/resources/someip.pcap"; - let fibex_file = "../../../developing/resources/someip.xml"; - - assert!( - PathBuf::from(fibex_file).exists(), - "Fibex file path doesn't exist. Path: {fibex_file}" - ); - - let parser_settings = stypes::SomeIpParserSettings { - fibex_file_paths: Some(vec![String::from(fibex_file)]), + let setup = SessionSetup { + origin: SessionAction::File(PathBuf::from("../../../developing/resources/someip.pcap")), + parser: parsers::someip::descriptor::get_default_options(Some(vec![PathBuf::from( + "../../../developing/resources/someip.xml", + )])), + source: sources::binary::pcap::legacy::get_default_options(), }; - let session_main_file = run_observe_session( - input, - stypes::FileFormat::PcapLegacy, - stypes::ParserType::SomeIp(parser_settings.clone()), - ) - .await; + let session_main_file = run_observe_session(setup.clone()).await; let session_files = SessionFiles::from_session_file(&session_main_file); insta::with_settings!({ description => "Snapshot for SomeIP file with Pcap Legacy byte source.", - info => &parser_settings, + info => &setup, omit_expression => true, prepend_module_to_snapshot => false, }, { diff --git a/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_dlt_session.snap b/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_dlt_session.snap index 5f6250169..b922fa16e 100644 --- a/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_dlt_session.snap +++ b/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_dlt_session.snap @@ -1,12 +1,25 @@ --- source: session/tests/snapshot_tests/mod.rs +assertion_line: 43 description: Snapshot for DLT file with text attachments. info: - filter_config: ~ - fibex_file_paths: ~ - with_storage_header: true - tz: ~ -snapshot_kind: text + origin: + File: "../../../developing/resources/attachments.dlt" + parser: + fields: + - id: DLT_PARSER_FIELD_FIBEX_FILES + value: + Files: [] + - id: DLT_PARSER_FIELD_LOG_LEVEL + value: + Number: 6 + - id: DLT_PARSER_FIELD_STATISTICS + value: + KeyStrings: {} + uuid: 01010101-0101-0101-0101-010101010101 + source: + fields: [] + uuid: 08080808-0808-0808-0808-080808080808 --- session_file: - "1970-01-01T00:00:00.000000000Z\u0004ecu\u00040\u0004\u00040\u0004\u0004ecu1\u0004\u0004\u0004LogLevel INFO\u0004\u0005FLST\u0005129721424\u0005test1.txt\u00055\u0005date\u00051\u00050\u0005FLST" diff --git a/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_dlt_with_someip_session.snap b/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_dlt_with_someip_session.snap index 14e9a15eb..df4481317 100644 --- a/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_dlt_with_someip_session.snap +++ b/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_dlt_with_someip_session.snap @@ -1,13 +1,26 @@ --- source: session/tests/snapshot_tests/mod.rs +assertion_line: 66 description: Snapshot for DLT file with SomeIP network trace. info: - filter_config: ~ - fibex_file_paths: - - "../../../developing/resources/someip.xml" - with_storage_header: true - tz: ~ -snapshot_kind: text + origin: + File: "../../../developing/resources/someip.dlt" + parser: + fields: + - id: DLT_PARSER_FIELD_FIBEX_FILES + value: + Files: + - "../../../developing/resources/someip.xml" + - id: DLT_PARSER_FIELD_LOG_LEVEL + value: + Number: 6 + - id: DLT_PARSER_FIELD_STATISTICS + value: + KeyStrings: {} + uuid: 01010101-0101-0101-0101-010101010101 + source: + fields: [] + uuid: 08080808-0808-0808-0808-080808080808 --- session_file: - "2024-02-20T13:17:26.713537000Z\u0004ECU1\u00041\u0004571\u0004204\u000428138506\u0004ECU1\u0004APP1\u0004C1\u0004IPC\u0004SOME/IP 0.0.0.0:0 >> INST:1 RPC SERV:123 METH:32773 LENG:16 CLID:0 SEID:58252 IVER:1 MSTP:2 RETC:0 TestService::timeEvent {\u0006\ttimestamp (INT64) : 1683656786973,\u0006}" diff --git a/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_someip_bcapng_session.snap b/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_someip_bcapng_session.snap index b68202b7f..04a93d75e 100644 --- a/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_someip_bcapng_session.snap +++ b/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_someip_bcapng_session.snap @@ -1,10 +1,20 @@ --- source: session/tests/snapshot_tests/mod.rs +assertion_line: 90 description: Snapshot for SomeIP file with Pcapng byte source. info: - fibex_file_paths: - - "../../../developing/resources/someip.xml" -snapshot_kind: text + origin: + File: "../../../developing/resources/someip.pcapng" + parser: + fields: + - id: SOMEIP_PARSER_FIELD_FIBEX_FILES + value: + Files: + - "../../../developing/resources/someip.xml" + uuid: 02020202-0202-0202-0202-020202020202 + source: + fields: [] + uuid: 09090909-0909-0909-0909-090909090909 --- session_file: - "SD\u000465535\u000433024\u000460\u00040\u00040\u00041\u00042\u00040\u0004Flags [C0], Offer 123 v1.0 Inst 1 Ttl 3 UDP 192.168.178.58:30000 TCP 192.168.178.58:30000" diff --git a/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_someip_legacy_session.snap b/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_someip_legacy_session.snap index 2557d86c5..d19debabd 100644 --- a/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_someip_legacy_session.snap +++ b/application/apps/indexer/session/tests/snapshot_tests/snapshots/observe_someip_legacy_session.snap @@ -1,10 +1,20 @@ --- source: session/tests/snapshot_tests/mod.rs +assertion_line: 114 description: Snapshot for SomeIP file with Pcap Legacy byte source. info: - fibex_file_paths: - - "../../../developing/resources/someip.xml" -snapshot_kind: text + origin: + File: "../../../developing/resources/someip.pcap" + parser: + fields: + - id: SOMEIP_PARSER_FIELD_FIBEX_FILES + value: + Files: + - "../../../developing/resources/someip.xml" + uuid: 02020202-0202-0202-0202-020202020202 + source: + fields: [] + uuid: 10101010-1010-1010-1010-101010101010 --- session_file: - "SD\u000465535\u000433024\u000460\u00040\u00040\u00041\u00042\u00040\u0004Flags [C0], Offer 123 v1.0 Inst 1 Ttl 3 UDP 192.168.178.58:30000 TCP 192.168.178.58:30000" diff --git a/application/apps/indexer/session/tests/snapshot_tests/utls.rs b/application/apps/indexer/session/tests/snapshot_tests/utls.rs index 4e58501c7..0c6aad5ed 100644 --- a/application/apps/indexer/session/tests/snapshot_tests/utls.rs +++ b/application/apps/indexer/session/tests/snapshot_tests/utls.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use session::session::Session; use std::path::{Path, PathBuf}; +use stypes::SessionSetup; use uuid::Uuid; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -89,28 +90,11 @@ fn session_dir_form_file(session_file: &Path) -> PathBuf { /// and returning the path of the main session file. /// # Note: /// This function it made for test purposes with snapshots. -pub async fn run_observe_session>( - input: P, - file_format: stypes::FileFormat, - parser_type: stypes::ParserType, -) -> PathBuf { - let input: PathBuf = input.into(); - - assert!( - input.exists(), - "Input file doesn't exist. Path {}", - input.display() - ); - +pub async fn run_observe_session(setup: SessionSetup) -> PathBuf { let uuid = Uuid::new_v4(); let (session, mut receiver) = Session::new(uuid).await.expect("Session should be created"); - session - .observe( - uuid, - stypes::ObserveOptions::file(input.clone(), file_format, parser_type), - ) - .unwrap(); + session.observe(uuid, setup).unwrap(); while let Some(feedback) = receiver.recv().await { match feedback { diff --git a/application/apps/indexer/sources/Cargo.toml b/application/apps/indexer/sources/Cargo.toml index 7433c73c7..5f642f985 100644 --- a/application/apps/indexer/sources/Cargo.toml +++ b/application/apps/indexer/sources/Cargo.toml @@ -8,7 +8,7 @@ edition = "2024" async-stream = "0.3" bufread = { path = "../addons/bufread" } bytes = "1.3" -etherparse = "0.16" +etherparse.workspace = true futures.workspace = true indexer_base = { path = "../indexer_base" } log.workspace = true @@ -24,48 +24,17 @@ uuid = { workspace = true , features = ["serde", "v4"] } regex.workspace = true lazy_static.workspace = true shellexpand = "3.1" -stypes = { path = "../stypes", features=["rustcore"] } socket2 = "0.5.8" +serialport.workspace = true +envvars.workspace = true + +file-tools = { path = "../addons/file-tools" } +stypes = { path = "../stypes", features=["rustcore"] } +descriptor = { path = "../descriptor" } [dev-dependencies] env_logger.workspace = true -criterion = { workspace = true, features = ["async_tokio"] } plugins_host = {path = "../plugins_host/"} toml.workspace = true -[[bench]] -name = "dlt_producer" -harness = false - -[[bench]] -name = "someip_producer" -harness = false - -[[bench]] -name = "someip_legacy_producer" -harness = false - -[[bench]] -name = "text_producer" -harness = false - -[[bench]] -name = "plugin_praser_producer" -harness = false - -[[bench]] -name = "mocks_once_producer" -harness = false - -[[bench]] -name = "mocks_once_parallel" -harness = false - -[[bench]] -name = "mocks_multi_producer" -harness = false - -[[bench]] -name = "mocks_multi_parallel" -harness = false diff --git a/application/apps/indexer/sources/src/api/mod.rs b/application/apps/indexer/sources/src/api/mod.rs new file mode 100644 index 000000000..0b6bc46fb --- /dev/null +++ b/application/apps/indexer/sources/src/api/mod.rs @@ -0,0 +1,178 @@ +use crate::*; +use stypes::SessionAction; +use thiserror::Error; + +/// Defines the function type used to construct a source instance for a session. +/// +/// This factory function is stored in the [`Register`] and invoked when a new session +/// is initialized. It produces a source instance based on the provided session context +/// and configuration fields. +/// +/// The return value is a tuple: +/// - [`Sources`] – An enum-wrapped instance of the constructed source. +/// - `Option` – A human-readable label that describes the configured source in context. +/// +/// The key difference between this label and the one returned by [`CommonDescriptor`] is +/// that this one is **contextual**. While `CommonDescriptor` may return a generic name such +/// as `"Serial port connector"`, this factory may return a context-specific name like +/// `"Serial on /dev/tty001"` that reflects actual settings. +/// +/// # Arguments +/// +/// * `&SessionAction` – The session action context (e.g., user-triggered session creation). +/// * `&[stypes::Field]` – A list of configuration fields provided by the client or system. +/// +/// # Returns +/// +/// * `Ok(Some((Sources, Option)))` – A constructed source instance with an optional +/// context-specific name for display. +/// * `Ok(None)` – Indicates that no source should be created (e.g., conditionally skipped). +/// * `Err(NativeError)` – If source creation fails due to invalid data or internal error. +/// +/// # Errors +/// +/// Returns an `Err(NativeError)` if the source cannot be created due to misconfiguration, +/// validation failure, or runtime errors during instantiation. +pub type SourceFactory = fn( + &SessionAction, + &[stypes::Field], +) -> Result)>, stypes::NativeError>; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TransportProtocol { + TCP, + UDP, + Unknown, +} + +impl From> for TransportProtocol { + fn from(tp_slice: etherparse::TransportSlice<'_>) -> Self { + match tp_slice { + etherparse::TransportSlice::Tcp(_) => TransportProtocol::TCP, + etherparse::TransportSlice::Udp(_) => TransportProtocol::UDP, + _ => TransportProtocol::Unknown, + } + } +} + +#[derive(Debug)] +pub struct SourceFilter { + pub transport: Option, +} + +#[derive(Debug)] +pub struct ReloadInfo { + pub newly_loaded_bytes: usize, + pub available_bytes: usize, + pub skipped_bytes: usize, + pub last_known_ts: Option, +} + +impl ReloadInfo { + pub fn new( + newly_loaded_bytes: usize, + available_bytes: usize, + skipped_bytes: usize, + last_known_ts: Option, + ) -> Self { + Self { + newly_loaded_bytes, + available_bytes, + skipped_bytes, + last_known_ts, + } + } +} + +#[derive(Error, Debug)] +pub enum SourceError { + #[error("Sources setup problem: {0}")] + Setup(String), + #[error("Unrecoverable source error: {0}")] + Unrecoverable(String), + #[error("IO error: {0}")] + Io(std::io::Error), + #[error("Not supported feature")] + NotSupported, +} + +impl From for stypes::NativeError { + fn from(err: SourceError) -> Self { + stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::ComputationFailed, + message: Some(format!("Fail create source: {err}")), + } + } +} + +pub const DEFAULT_READER_CAPACITY: usize = 10 * 1024 * 1024; +pub const DEFAULT_MIN_BUFFER_SPACE: usize = 10 * 1024; + +/// Requirements to `ByteSource` inner reader +pub trait InnerReader: std::io::Read + Send + Unpin + 'static {} + +/// In summary, this decision prioritizes architectural flexibility, reduces long-term maintenance +/// risks, and ensures code stability, making the overall system more sustainable and extensible +/// in the long run. +/// +/// A `ByteSource` provides a way to read data from some underlying data source. But it does +/// not provide a simple read interface, rather it allows implementations to filter the data +/// while reading it from it's underlying source. +/// A good example is a network trace where complete ethernet frames are described. If we only +/// want to extract the data part from certain frames, the `relaod` method will load only the relevant +/// data into an internal buffer. +/// This data can then be accessed via the `current_slice` method. +#[allow(async_fn_in_trait)] +pub trait ByteSource: Send { + /// Indicate that we have consumed a certain amount of data from our internal + /// buffer and that this part can be discarded + fn consume(&mut self, offset: usize); + + /// Provide access to the filtered data that is currently loaded + fn current_slice(&self) -> &[u8]; + + /// count of currently loaded bytes + fn len(&self) -> usize; + + fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// will load more bytes from the underlying source + /// when the source has reached it's end, this function + /// will return Ok((None, _)) + /// + /// A successful reload operation will return the number + /// of bytes that were newly loaded `newly_loaded_bytes` + /// along with all currently available bytes `available_bytes` + /// In some cases it is possible that some bytes had to be skipped in order to + /// reach the next usable bytes, this is indicated in the `skipped_bytes` number + /// + /// + /// If the source has access to some timestamp (e.g. timestamp of network package), + /// this timestamp is passed on additionally (`last_known_ts`) + /// + /// # Note: + /// + /// This function must be **Cancel-Safe** + async fn load( + &mut self, + filter: Option<&SourceFilter>, + ) -> Result, SourceError>; + + /// In case the ByteSource is some kind of connection that does not end, + /// cancel can be implemented that will give the ByteSource the chance to perform some + /// cleanup before the ByteSource is discarded + async fn cancel(&mut self) -> Result<(), SourceError> { + Ok(()) + } + + /// Append incoming (SDE) Source-Data-Exchange to the data. + async fn income( + &mut self, + _msg: stypes::SdeRequest, + ) -> Result { + Err(SourceError::NotSupported) + } +} diff --git a/application/apps/indexer/sources/src/binary/pcap/legacy/descriptor.rs b/application/apps/indexer/sources/src/binary/pcap/legacy/descriptor.rs new file mode 100644 index 000000000..98ae3ba1c --- /dev/null +++ b/application/apps/indexer/sources/src/binary/pcap/legacy/descriptor.rs @@ -0,0 +1,157 @@ +use descriptor::{CommonDescriptor, SourceDescriptor, SourceFactory}; +use file_tools::is_binary; +use stypes::{ComponentOptions, NativeError, NativeErrorKind, SessionAction, Severity}; +use crate::*; +use super::PcapLegacyByteSourceFromFile; + +const PCAP_SOURCE_UUID: uuid::Uuid = uuid::Uuid::from_bytes([ + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, +]); + +#[derive(Default)] +pub struct Descriptor {} + +impl SourceFactory for Descriptor { + fn create( + &self, + origin: &stypes::SessionAction, + _options: &[stypes::Field], + ) -> Result)>, stypes::NativeError> { + let filepath = match origin { + SessionAction::File(file) => file, + SessionAction::Files(..) | SessionAction::Source | SessionAction::ExportRaw(.. ) => { + return Err(NativeError { + severity: Severity::ERROR, + kind: NativeErrorKind::Configuration, + message: Some("Pcap Legacy Source cannot be applied in this context".to_owned()) + }) + } + }; + Ok(Some((Sources::Pcap(PcapLegacyByteSourceFromFile::new(filepath)?), Some("Pcap".to_owned())))) + } +} + +impl CommonDescriptor for Descriptor { + fn is_compatible(&self, origin: &SessionAction) -> bool { + let files = match origin { + SessionAction::File(filepath) => { + vec![filepath] + } + SessionAction::Files(files) => files.iter().collect(), + SessionAction::Source | SessionAction::ExportRaw(..) => { + return false; + } + }; + files.iter().any(|fp| { + fp.extension() + .map(|ext| ext.eq_ignore_ascii_case("pcap")) + .unwrap_or_default() + }) && + // If at least some file doesn't exist or not binary - do not recommend this source + !files + .into_iter() + .any(|f| !f.exists() || !is_binary(f.to_string_lossy().to_string()).unwrap_or_default()) + } + fn ident(&self) -> stypes::Ident { + stypes::Ident { + name: String::from("PCAP Source"), + desc: String::from("PCAP Source"), + io: stypes::IODataType::NetworkFramePayload, + uuid: PCAP_SOURCE_UUID, + } + } + +} + +impl SourceDescriptor for Descriptor {} + +pub fn get_default_options() -> ComponentOptions { + ComponentOptions { + uuid: PCAP_SOURCE_UUID, + fields: Vec::new(), + } +} + +#[cfg(test)] +mod tests { + + use env_logger; + + use crate::{ + binary::pcap::legacy::PcapLegacyByteSource, tests::general_source_reload_test, ByteSource, + }; + + #[tokio::test(flavor = "multi_thread")] + async fn test_read_one_message_from_pcap() { + let _ = env_logger::try_init(); + + const SAMPLE_PCAP_DATA: &[u8] = &[ + 0xd4, 0xc3, 0xb2, 0xa1, // Magic Number (4 bytes) = d4 c3 b2 a1 + 0x02, 0x00, // Version Major (2 bytes) = 02 00 + 0x04, 0x00, // Version Minor (2 bytes) = 04 00 + 0x00, 0x00, 0x00, 0x00, // Timezone (4 bytes) = 00 00 00 00 + 0x00, 0x00, 0x00, 0x00, // Timestamp Accuracy (4 bytes) = 00 00 00 00 + 0x00, 0x00, 0x04, 0x00, // Snap Length (4 bytes) + 0x01, 0x00, 0x00, 0x00, // Link-Layer Type (4 bytes) + // Packet Header 16 byte + 0xeb, 0x15, 0x88, 0x60, 0xe6, 0x7f, 0x04, 0x00, 0x62, 0x00, 0x00, 0x00, 0x62, 0x00, + 0x00, 0x00, // + // start of ethernet packet ------------------- + 0xb8, 0x27, 0xeb, 0x1d, 0x24, 0xc9, 0xb8, 0x27, 0xeb, 0x98, 0x94, 0xfa, 0x08, 0x00, + 0x45, 0x00, 0x00, 0x54, 0xa0, 0x48, 0x40, 0x00, 0x40, 0x11, 0x29, 0x85, 0xac, 0x16, + 0x0c, 0x4f, 0xac, 0x16, 0x0c, + 0x50, // start of udp frame ------------------------- + 0xc3, 0x50, 0xc3, 0x50, 0x00, 0x40, 0x8e, 0xe3, // + // start of udp payload 56 bytes --------------------------- + 0xff, 0xff, 0x81, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, + 0x02, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x10, + 0x01, 0x03, 0x00, 0x01, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0c, 0x00, 0x09, 0x04, 0x00, 0xac, 0x16, 0x0c, 0x4f, 0x00, 0x11, 0x75, 0x30, + ]; + + let udp_payload = &SAMPLE_PCAP_DATA[82..=137]; + let pcap_file = std::io::Cursor::new(SAMPLE_PCAP_DATA); + + let mut source = PcapLegacyByteSource::new(pcap_file).expect("cannot create source"); + let reload_info = source.load(None).await.expect("reload should work"); + println!("reload_info: {:?}", reload_info); + let slice = source.current_slice(); + println!("slice: {:x?}", slice); + assert_eq!(slice.len(), 56); + assert_eq!(slice, udp_payload); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_general_source_reload() { + // This is part of the file "chipmunk/application/developing/resources". + // In this test we just need enough bytes to call reload twice on it, and we will not + // call parse on any of this data. + const SAMPLE_PCAP_DATA: &[u8] = &[ + 0xd4, 0xc3, 0xb2, 0xa1, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x52, 0x90, 0x5a, 0x64, + 0xa4, 0xd8, 0x0e, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x5e, 0x40, 0xff, 0xfb, 0xb8, 0x27, 0xeb, 0x1d, 0x24, 0xc9, 0x08, 0x00, 0x45, 0x00, + 0x00, 0x60, 0x16, 0x99, 0x00, 0x00, 0x01, 0x11, 0x40, 0x17, 0xc0, 0xa8, 0xb2, 0x3a, + 0xef, 0xff, 0xff, 0xfa, 0x9c, 0x40, 0x9c, 0x40, 0x00, 0x4c, 0x63, 0x3b, 0xff, 0xff, + 0x81, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x20, 0x00, 0x7b, + 0x00, 0x01, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, + 0x00, 0x09, 0x04, 0x00, 0xc0, 0xa8, 0xb2, 0x3a, 0x00, 0x11, 0x75, 0x30, 0x00, 0x10, + 0x04, 0x00, 0xc0, 0xa8, 0xb2, 0x3a, 0x00, 0x06, 0x75, 0x30, 0x52, 0x90, 0x5a, 0x64, + 0x08, 0xda, 0x0e, 0x00, 0x62, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x5e, 0x40, 0xff, 0xfb, 0xb8, 0x27, 0xeb, 0x1d, 0x24, 0xc9, 0x08, 0x00, 0x45, 0x00, + 0x00, 0x54, 0x3a, 0xb4, 0x00, 0x00, 0x40, 0x11, 0x00, 0x00, 0xc0, 0xa8, 0xb2, 0x3a, + 0xc0, 0xa8, 0xb2, 0x3a, 0x9c, 0x40, 0x9c, 0x40, 0x00, 0x40, 0xe6, 0x17, 0xff, 0xff, + 0x81, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x06, 0x00, 0x00, 0x10, 0x00, 0x7b, + 0x00, 0x01, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x41, 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x09, 0x04, 0x00, 0xc0, 0xa8, 0xb2, 0x3a, 0x00, 0x11, 0x75, 0x30, 0x52, 0x90, + ]; + + let pcap_file = std::io::Cursor::new(SAMPLE_PCAP_DATA); + + let mut source = PcapLegacyByteSource::new(pcap_file).expect("cannot create source"); + + general_source_reload_test(&mut source).await; + } +} diff --git a/application/apps/indexer/sources/src/binary/pcap/legacy.rs b/application/apps/indexer/sources/src/binary/pcap/legacy/mod.rs similarity index 86% rename from application/apps/indexer/sources/src/binary/pcap/legacy.rs rename to application/apps/indexer/sources/src/binary/pcap/legacy/mod.rs index 54264fbe8..8c56b871b 100644 --- a/application/apps/indexer/sources/src/binary/pcap/legacy.rs +++ b/application/apps/indexer/sources/src/binary/pcap/legacy/mod.rs @@ -1,12 +1,14 @@ -use crate::{ - ByteSource, Error as SourceError, ReloadInfo, SourceFilter, TransportProtocol, - binary::pcap::debug_block, -}; +mod descriptor; + +use crate::binary::pcap::debug_block; +use crate::*; use bufread::DeqBuffer; use etherparse::{SlicedPacket, TransportSlice}; use log::{debug, error, trace}; use pcap_parser::{LegacyPcapReader, PcapBlockOwned, PcapError, traits::PcapReaderIterator}; -use std::io::Read; +use std::{fs::File, io::Read, path::Path}; + +pub use descriptor::*; pub struct PcapLegacyByteSource { pcap_reader: LegacyPcapReader, @@ -155,6 +157,55 @@ impl ByteSource for PcapLegacyByteSource { } } +pub struct PcapLegacyByteSourceFromFile { + inner: PcapLegacyByteSource, +} + +impl PcapLegacyByteSourceFromFile { + pub fn new>(filename: P) -> Result { + fn input_file(filename: &Path) -> Result { + File::open(filename).map_err(|e| stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Io, + message: Some(format!( + "Fail open file {}: {}", + filename.to_string_lossy(), + e + )), + }) + } + Ok(Self { + inner: PcapLegacyByteSource::new(input_file(filename.as_ref())?).map_err(|err| { + stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Io, + message: Some(err.to_string()), + } + })?, + }) + } +} + +impl ByteSource for PcapLegacyByteSourceFromFile { + async fn load( + &mut self, + filter: Option<&SourceFilter>, + ) -> Result, SourceError> { + self.inner.load(filter).await + } + + fn current_slice(&self) -> &[u8] { + self.inner.current_slice() + } + fn consume(&mut self, offset: usize) { + self.inner.consume(offset) + } + + fn len(&self) -> usize { + self.inner.len() + } +} + #[cfg(test)] mod tests { @@ -219,7 +270,7 @@ mod tests { 0x81, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x20, 0x00, 0x7b, 0x00, 0x01, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, - 0x00, 0x09, 0x04, 0x00, 0xc0, 0xa8, 0xb2, 0x3a, 0x00, 0x11, 0x75, 0x30, 0x00, 0x09, + 0x00, 0x09, 0x04, 0x00, 0xc0, 0xa8, 0xb2, 0x3a, 0x00, 0x11, 0x75, 0x30, 0x00, 0x10, 0x04, 0x00, 0xc0, 0xa8, 0xb2, 0x3a, 0x00, 0x06, 0x75, 0x30, 0x52, 0x90, 0x5a, 0x64, 0x08, 0xda, 0x0e, 0x00, 0x62, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x01, 0x00, 0x5e, 0x40, 0xff, 0xfb, 0xb8, 0x27, 0xeb, 0x1d, 0x24, 0xc9, 0x08, 0x00, 0x45, 0x00, diff --git a/application/apps/indexer/sources/src/binary/pcap/ng/descriptor.rs b/application/apps/indexer/sources/src/binary/pcap/ng/descriptor.rs new file mode 100644 index 000000000..03782887c --- /dev/null +++ b/application/apps/indexer/sources/src/binary/pcap/ng/descriptor.rs @@ -0,0 +1,73 @@ +use descriptor::{CommonDescriptor, SourceDescriptor, SourceFactory}; +use file_tools::is_binary; +use stypes::{ComponentOptions, NativeError, NativeErrorKind, SessionAction, Severity}; +use super::PcapngByteSourceFromFile; +use crate::*; + +const PCAPNG_SOURCE_UUID: uuid::Uuid = uuid::Uuid::from_bytes([ + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, +]); + +#[derive(Default)] +pub struct Descriptor {} + +impl SourceFactory for Descriptor { + fn create( + &self, + origin: &stypes::SessionAction, + _options: &[stypes::Field], + ) -> Result)>, stypes::NativeError> { + let filepath = match origin { + SessionAction::File(file) => file, + SessionAction::Files(..) | SessionAction::Source | SessionAction::ExportRaw(..) => { + return Err(NativeError { + severity: Severity::ERROR, + kind: NativeErrorKind::Configuration, + message: Some("Pcap NG Source cannot be applied in this context".to_owned()) + }) + } + }; + Ok(Some((Sources::PcapNg(PcapngByteSourceFromFile::new(filepath)?), Some("PcapNg".to_owned())))) + } +} + +impl CommonDescriptor for Descriptor { + fn is_compatible(&self, origin: &SessionAction) -> bool { + let files = match origin { + SessionAction::File(filepath) => { + vec![filepath] + } + SessionAction::Files(files) => files.iter().collect(), + SessionAction::Source | SessionAction::ExportRaw(..) => { + return false; + } + }; + files.iter().any(|fp| { + fp.extension() + .map(|ext| ext.eq_ignore_ascii_case("pcapng")) + .unwrap_or_default() + }) && + // If at least some file doesn't exist or not binary - do not recommend this source + !files + .into_iter() + .any(|f| !f.exists() || !is_binary(f.to_string_lossy().to_string()).unwrap_or_default()) + } + fn ident(&self) -> stypes::Ident { + stypes::Ident { + name: String::from("PCAP NG Source"), + desc: String::from("PCAP NG Source"), + io: stypes::IODataType::NetworkFramePayload, + uuid: PCAPNG_SOURCE_UUID, + } + } +} + +impl SourceDescriptor for Descriptor {} + + +pub fn get_default_options() -> ComponentOptions { + ComponentOptions { + uuid: PCAPNG_SOURCE_UUID, + fields: Vec::new(), + } +} \ No newline at end of file diff --git a/application/apps/indexer/sources/src/binary/pcap/ng.rs b/application/apps/indexer/sources/src/binary/pcap/ng/mod.rs similarity index 85% rename from application/apps/indexer/sources/src/binary/pcap/ng.rs rename to application/apps/indexer/sources/src/binary/pcap/ng/mod.rs index 2e7c6a073..417b9521a 100644 --- a/application/apps/indexer/sources/src/binary/pcap/ng.rs +++ b/application/apps/indexer/sources/src/binary/pcap/ng/mod.rs @@ -1,12 +1,14 @@ -use crate::{ - ByteSource, Error as SourceError, ReloadInfo, SourceFilter, TransportProtocol, - binary::pcap::debug_block, -}; +mod descriptor; + +use crate::binary::pcap::debug_block; +use crate::*; use bufread::DeqBuffer; use etherparse::{SlicedPacket, TransportSlice}; use log::{debug, error, trace}; use pcap_parser::{PcapBlockOwned, PcapError, PcapNGReader, traits::PcapReaderIterator}; -use std::io::Read; +use std::{fs::File, io::Read, path::Path}; + +pub use descriptor::*; pub struct PcapngByteSource { pcapng_reader: PcapNGReader, @@ -159,12 +161,61 @@ impl ByteSource for PcapngByteSource { } } +pub struct PcapngByteSourceFromFile { + inner: PcapngByteSource, +} + +impl PcapngByteSourceFromFile { + pub fn new>(filename: P) -> Result { + fn input_file(filename: &Path) -> Result { + File::open(filename).map_err(|e| stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Io, + message: Some(format!( + "Fail open file {}: {}", + filename.to_string_lossy(), + e + )), + }) + } + Ok(Self { + inner: PcapngByteSource::new(input_file(filename.as_ref())?).map_err(|err| { + stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Io, + message: Some(err.to_string()), + } + })?, + }) + } +} + +impl ByteSource for PcapngByteSourceFromFile { + async fn load( + &mut self, + filter: Option<&SourceFilter>, + ) -> Result, SourceError> { + self.inner.load(filter).await + } + + fn current_slice(&self) -> &[u8] { + self.inner.current_slice() + } + fn consume(&mut self, offset: usize) { + self.inner.consume(offset) + } + + fn len(&self) -> usize { + self.inner.len() + } +} + #[cfg(test)] mod tests { use crate::{ ByteSource, binary::pcap::ng::PcapngByteSource, - tests::{general_source_reload_test, mock_read::MockRepeatRead}, + tests::{MockRepeatRead, general_source_reload_test}, }; use env_logger; diff --git a/application/apps/indexer/sources/src/binary/raw/descriptor.rs b/application/apps/indexer/sources/src/binary/raw/descriptor.rs new file mode 100644 index 000000000..1ff632aec --- /dev/null +++ b/application/apps/indexer/sources/src/binary/raw/descriptor.rs @@ -0,0 +1,72 @@ +use crate::*; +use descriptor::{CommonDescriptor, SourceDescriptor, SourceFactory}; +use file_tools::is_binary; +use stypes::{ComponentOptions, SessionAction}; + +const BIN_SOURCE_UUID: uuid::Uuid = uuid::Uuid::from_bytes([ + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, +]); + +#[derive(Default)] +pub struct Descriptor {} + +impl SourceFactory for Descriptor { + fn create( + &self, + origin: &stypes::SessionAction, + _options: &[stypes::Field], + ) -> Result)>, stypes::NativeError> { + let filename = match origin { + SessionAction::File(filename) => filename, + SessionAction::Files(..) | SessionAction::Source | SessionAction::ExportRaw(..) => { + return Err(stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Configuration, + message: Some( + "origin isn't supported by this source (BinaryByteSource)".to_owned(), + ), + }); + } + }; + + Ok(Some(( + Sources::Raw(super::BinaryByteSourceFromFile::new(filename)?), + Some(filename.to_string_lossy().to_string()), + ))) + } +} + +impl CommonDescriptor for Descriptor { + fn is_compatible(&self, origin: &SessionAction) -> bool { + let files = match origin { + SessionAction::File(filepath) => { + vec![filepath] + } + SessionAction::Files(files) => files.iter().collect(), + SessionAction::Source | SessionAction::ExportRaw(..) => { + return false; + } + }; + // If at least some file doesn't exist or not binary - do not recommend this source + !files + .into_iter() + .any(|f| !f.exists() || !is_binary(f.to_string_lossy().to_string()).unwrap_or_default()) + } + fn ident(&self) -> stypes::Ident { + stypes::Ident { + name: String::from("Binary Source"), + desc: String::from("Binary Source"), + io: stypes::IODataType::Raw, + uuid: BIN_SOURCE_UUID, + } + } +} + +impl SourceDescriptor for Descriptor {} + +pub fn get_default_options() -> ComponentOptions { + ComponentOptions { + uuid: BIN_SOURCE_UUID, + fields: Vec::new(), + } +} diff --git a/application/apps/indexer/sources/src/binary/raw.rs b/application/apps/indexer/sources/src/binary/raw/mod.rs similarity index 76% rename from application/apps/indexer/sources/src/binary/raw.rs rename to application/apps/indexer/sources/src/binary/raw/mod.rs index 38c20a4e5..25e51a98d 100644 --- a/application/apps/indexer/sources/src/binary/raw.rs +++ b/application/apps/indexer/sources/src/binary/raw/mod.rs @@ -1,9 +1,13 @@ -use crate::{ - ByteSource, DEFAULT_MIN_BUFFER_SPACE, DEFAULT_READER_CAPACITY, Error as SourceError, - ReloadInfo, SourceFilter, -}; +mod descriptor; + +use crate::*; use bufread::BufReader; -use std::io::{BufRead, Read}; +pub use descriptor::*; +use std::{ + fs::File, + io::{BufRead, Read}, + path::Path, +}; pub struct BinaryByteSource where @@ -76,6 +80,49 @@ impl ByteSource for BinaryByteSource { } } +pub struct BinaryByteSourceFromFile { + inner: BinaryByteSource, +} + +impl BinaryByteSourceFromFile { + pub fn new>(filename: P) -> Result { + fn input_file(filename: &Path) -> Result { + File::open(filename).map_err(|e| stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Io, + message: Some(format!( + "Fail open file {}: {}", + filename.to_string_lossy(), + e + )), + }) + } + Ok(Self { + inner: BinaryByteSource::new(input_file(filename.as_ref())?), + }) + } +} + +impl ByteSource for BinaryByteSourceFromFile { + async fn load( + &mut self, + filter: Option<&SourceFilter>, + ) -> Result, SourceError> { + self.inner.load(filter).await + } + + fn current_slice(&self) -> &[u8] { + self.inner.current_slice() + } + fn consume(&mut self, offset: usize) { + self.inner.consume(offset) + } + + fn len(&self) -> usize { + self.inner.len() + } +} + #[cfg(test)] mod tests { use crate::{ diff --git a/application/apps/indexer/sources/src/command/descriptor.rs b/application/apps/indexer/sources/src/command/descriptor.rs new file mode 100644 index 000000000..a2e7b79f6 --- /dev/null +++ b/application/apps/indexer/sources/src/command/descriptor.rs @@ -0,0 +1,134 @@ +use super::ProcessSource; +use crate::*; +use descriptor::{CommonDescriptor, FieldsResult, SourceDescriptor, SourceFactory}; +use std::{collections::HashMap, env}; +use stypes::{ + ExtractByKey, Extracted, Field, FieldDesc, NativeError, NativeErrorKind, SessionAction, + Severity, StaticFieldDesc, ValueInput, missed_field_err as missed, +}; + +const TERM_SOURCE_UUID: uuid::Uuid = uuid::Uuid::from_bytes([ + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, +]); + +const FIELD_COMMAND: &str = "COMMAND_FIELD_COMMAND"; +const FIELD_CWD: &str = "COMMAND_FIELD_CWD"; +const FIELD_SHELLS: &str = "COMMAND_FIELD_SHELLS"; + +#[derive(Default)] +pub struct Descriptor {} + +impl SourceFactory for Descriptor { + fn create( + &self, + origin: &stypes::SessionAction, + options: &[stypes::Field], + ) -> Result)>, stypes::NativeError> { + let errors = self.validate(origin, options)?; + if !errors.is_empty() { + return Err(NativeError { + kind: NativeErrorKind::Configuration, + severity: Severity::ERROR, + message: Some( + errors + .values() + .map(String::as_str) + .collect::>() + .join("; "), + ), + }); + } + let command: String = options + .extract_by_key(FIELD_COMMAND) + .ok_or(missed(FIELD_COMMAND))? + .value; + Ok(Some(( + Sources::Process( + ProcessSource::new(&command, env::current_dir().unwrap(), HashMap::new()).unwrap(), + ), + Some(command), + ))) + } +} + +impl CommonDescriptor for Descriptor { + fn fields_getter(&self, _origin: &SessionAction) -> FieldsResult { + let mut shells = vec![("".to_owned(), "Default".to_owned())]; + if let Ok(profiles) = envvars::get_profiles() { + shells = profiles + .into_iter() + .map(|profile| { + let path = profile.path.to_string_lossy(); + (format!("{} ({path})", profile.name), path.to_string()) + }) + .collect(); + } + Ok(vec![ + FieldDesc::Static(StaticFieldDesc { + id: FIELD_COMMAND.to_owned(), + name: "Terminal Command".to_owned(), + desc: String::new(), + required: true, + interface: ValueInput::String(String::new(), "terminal command".to_owned()), + binding: None, + }), + FieldDesc::Static(StaticFieldDesc { + id: FIELD_CWD.to_owned(), + name: "Working Folder".to_owned(), + desc: String::new(), + required: true, + interface: ValueInput::Directory( + env::current_dir() + .map(|cwd| Some(cwd.to_string_lossy().to_string())) + .unwrap_or(None), + ), + binding: None, + }), + FieldDesc::Static(StaticFieldDesc { + id: FIELD_SHELLS.to_owned(), + name: "Shell".to_owned(), + desc: String::new(), + required: true, + interface: ValueInput::NamedStrings(shells, String::new()), + binding: None, + }), + ]) + } + fn is_compatible(&self, origin: &SessionAction) -> bool { + match origin { + SessionAction::File(..) | SessionAction::Files(..) | SessionAction::ExportRaw(..) => { + false + } + SessionAction::Source => true, + } + } + fn ident(&self) -> stypes::Ident { + stypes::Ident { + name: String::from("Command Output"), + desc: String::from( + "Reads the standard output (stdout) of a launched command. The data is passed to the parser as lines of text.", + ), + io: stypes::IODataType::PlaitText, + uuid: TERM_SOURCE_UUID, + } + } + fn validate( + &self, + _origin: &SessionAction, + fields: &[Field], + ) -> Result, NativeError> { + let mut errors = HashMap::new(); + let command: Extracted = fields + .extract_by_key(FIELD_COMMAND) + .ok_or(missed(FIELD_COMMAND))?; + if command.value.trim().is_empty() { + errors.insert(command.id.to_owned(), "command cannot be empty".to_owned()); + } + Ok(errors) + } +} +impl SourceDescriptor for Descriptor { + fn is_sde_supported(&self, _origin: &stypes::SessionAction) -> bool { + true + } +} diff --git a/application/apps/indexer/sources/src/command/mod.rs b/application/apps/indexer/sources/src/command/mod.rs index 80fe812c8..2894f17c0 100644 --- a/application/apps/indexer/sources/src/command/mod.rs +++ b/application/apps/indexer/sources/src/command/mod.rs @@ -1 +1,300 @@ -pub mod process; +mod descriptor; +pub use descriptor::*; + +use crate::*; +use bufread::DeqBuffer; +use regex::{Captures, Regex}; +use shellexpand::tilde; +use std::{collections::HashMap, ffi::OsString, path::PathBuf, process::Stdio}; +use thiserror::Error; +use tokio::{ + io::AsyncWriteExt, + process::{Child, ChildStderr, ChildStdin, ChildStdout, Command}, + select, +}; +use tokio_stream::StreamExt; +use tokio_util::codec::{self, FramedRead, LinesCodec}; + +lazy_static! { + static ref GROUP_RE: Regex = + Regex::new(r#"".*?""#).expect("Regex must compile (fail with GROUP_RE)"); + static ref QUOTE_RE: Regex = + Regex::new(r#"""#).expect("Regex must compile (fail with QUOTE_RE)"); + static ref ESC_RE: Regex = Regex::new(r"\\\s").expect("Regex must compile (fail with ESC_RE)"); +} + +#[derive(Error, Debug)] +pub enum ProcessError { + #[error("{0}")] + Setup(String), + #[error("Unrecoverable process error: {0}")] + Unrecoverable(String), +} + +pub struct ProcessSource { + process: Child, + buffer: DeqBuffer, + stdout: FramedRead, + stderr: FramedRead, + stdin: ChildStdin, +} + +impl Drop for ProcessSource { + fn drop(&mut self) { + let is_process_alive = self.process.try_wait().is_ok_and(|state| state.is_none()); + if is_process_alive { + if let Err(err) = self.process.start_kill() { + warn!("Fail to kill child process: {err}"); + } + } + } +} + +impl ProcessSource { + pub fn parse_command(command: &str) -> Result, ProcessError> { + let mut groups: Vec = vec![]; + let parsed = ESC_RE.replace_all(command, "==esc_space==").to_string(); + let parsed = GROUP_RE.replace_all(&parsed, |caps: &Captures| { + let index = groups.len(); + if caps.len() != 0 { + let group = caps[0].to_string(); + groups.push(QUOTE_RE.replace_all(&group, "").to_string()); + } + format!("==extraction:({index})==") + }); + Ok(parsed + .split(' ') + .map(|a| { + let mut str = a.to_string(); + for (i, g) in groups.iter().enumerate() { + let key = format!("==extraction:({i})=="); + str.replace(&key, g).clone_into(&mut str); + } + let restored = str.replace("==esc_space==", " "); + OsString::from(tilde(&restored).to_string()) + }) + .collect()) + } + + #[cfg(windows)] + fn spawn( + cmd: OsString, + args: Vec, + cwd: PathBuf, + envs: HashMap, + ) -> Result { + const CREATE_NO_WINDOW: u32 = 0x08000000; + Command::new(cmd) + .args(args) + .current_dir(OsString::from(cwd)) + .envs(envs) + .creation_flags(CREATE_NO_WINDOW) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .stdin(Stdio::piped()) + .kill_on_drop(true) + .spawn() + .map_err(|e| ProcessError::Setup(format!("{e}"))) + } + + #[cfg(not(windows))] + fn spawn( + cmd: OsString, + args: Vec, + cwd: PathBuf, + envs: HashMap, + ) -> Result { + Command::new(cmd) + .args(args) + .current_dir(OsString::from(cwd)) + .envs(envs) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .stdin(Stdio::piped()) + .kill_on_drop(true) + .spawn() + .map_err(|e| ProcessError::Setup(format!("{e}"))) + } + + pub fn new( + command: &str, + cwd: PathBuf, + envs: HashMap, + ) -> Result { + let mut args = ProcessSource::parse_command(command)?; + let cmd = if args.is_empty() { + return Err(ProcessError::Setup(format!( + "Not command has been found in \"{command}\"" + ))); + } else { + args.remove(0) + }; + let mut process = ProcessSource::spawn(cmd, args, cwd, envs)?; + let stdout = codec::FramedRead::new( + process + .stdout + .take() + .ok_or_else(|| ProcessError::Setup(String::from("Fail to get stdout handle")))?, + LinesCodec::default(), + ); + let stderr = codec::FramedRead::new( + process + .stderr + .take() + .ok_or_else(|| ProcessError::Setup(String::from("Fail to get stderr handle")))?, + LinesCodec::default(), + ); + let stdin = process + .stdin + .take() + .ok_or_else(|| ProcessError::Setup(String::from("Fail to get stdin handle")))?; + Ok(Self { + process, + buffer: DeqBuffer::new(8192), + stdout, + stderr, + stdin, + }) + } +} + +impl ByteSource for ProcessSource { + async fn load( + &mut self, + _filter: Option<&SourceFilter>, + ) -> Result, SourceError> { + let mut closing = false; + let mut output; + // Implementation is cancel-safe here because there is no data gathered between to await + // calls. The only multiple await calls here, are actually while closing where there is + // no data to lose anyway. + loop { + if !closing { + output = select! { + res = self.stdout.next() => res, + res = self.stderr.next() => { + if res.is_none() { + closing = true; + } + res + }, + }; + if !closing { + break; + } + } else { + output = self.stdout.next().await; + break; + } + } + if let Some(Ok(line)) = output { + let stored = line.len() + 1; + self.buffer.write_from(line.as_bytes()); + self.buffer.write_from(b"\n"); + let available_bytes = self.buffer.read_available(); + Ok(Some(ReloadInfo::new(stored, available_bytes, 0, None))) + } else if let Some(Err(err)) = output { + Err(SourceError::Unrecoverable(format!("{err}"))) + } else { + Ok(None) + } + } + + fn current_slice(&self) -> &[u8] { + self.buffer.read_slice() + } + + fn consume(&mut self, offset: usize) { + self.buffer.read_done(offset); + } + + fn len(&self) -> usize { + self.buffer.read_available() + } + + fn is_empty(&self) -> bool { + self.len() == 0 + } + + async fn income( + &mut self, + request: stypes::SdeRequest, + ) -> Result { + let bytes = match request { + stypes::SdeRequest::WriteText(ref str) => str.as_bytes(), + stypes::SdeRequest::WriteBytes(ref bytes) => bytes, + }; + self.stdin.write_all(bytes).await.map_err(SourceError::Io)?; + Ok(stypes::SdeResponse { bytes: bytes.len() }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::general_source_reload_test; + + #[tokio::test] + async fn test_process() -> Result<(), ProcessError> { + use std::env; + let mut command = ""; + if cfg!(windows) { + command = "help"; + } else if cfg!(unix) { + command = "ls -lsa"; + } + let envs = HashMap::new(); + match ProcessSource::new(command, env::current_dir().unwrap(), envs) { + Ok(mut process_source) => { + while process_source + .load(None) + .await + .expect("Reload data from process source failed") + .is_some() + { + assert!(!process_source.current_slice().is_empty()); + process_source.consume(process_source.current_slice().len()); + } + // By some reasons during test sometimes process stay alive and as result + let _ = process_source.process.kill().await; + Ok(()) + } + Err(err) => Err(err), + } + } + + #[tokio::test] + async fn test_parsing() -> Result<(), ProcessError> { + let parsed = + ProcessSource::parse_command(r#"cmd arg2 "some_path/with space or spaces" arg3"#)?; + assert_eq!(parsed.len(), 4); + assert_eq!(parsed[0], OsString::from("cmd")); + assert_eq!(parsed[1], OsString::from("arg2")); + assert_eq!(parsed[2], OsString::from("some_path/with space or spaces")); + assert_eq!(parsed[3], OsString::from("arg3")); + let parsed = + ProcessSource::parse_command(r"cmd arg2 some_path/with\ space\ or\ spaces arg3")?; + assert_eq!(parsed.len(), 4); + assert_eq!(parsed[0], OsString::from("cmd")); + assert_eq!(parsed[1], OsString::from("arg2")); + assert_eq!(parsed[2], OsString::from("some_path/with space or spaces")); + assert_eq!(parsed[3], OsString::from("arg3")); + Ok(()) + } + + #[tokio::test] + async fn test_source_reload() { + use std::env; + let mut command = ""; + if cfg!(windows) { + command = "help"; + } else if cfg!(unix) { + command = "ls -lsa"; + } + let envs = HashMap::new(); + let mut process_source = + ProcessSource::new(command, env::current_dir().unwrap(), envs).unwrap(); + + general_source_reload_test(&mut process_source).await; + } +} diff --git a/application/apps/indexer/sources/src/command/process.rs b/application/apps/indexer/sources/src/command/process.rs index 1fadf6af8..8b1378917 100644 --- a/application/apps/indexer/sources/src/command/process.rs +++ b/application/apps/indexer/sources/src/command/process.rs @@ -1,299 +1 @@ -use crate::{ByteSource, Error as SourceError, ReloadInfo, SourceFilter}; -use bufread::DeqBuffer; -use regex::{Captures, Regex}; -use shellexpand::tilde; -use std::{collections::HashMap, ffi::OsString, path::PathBuf, process::Stdio}; -use thiserror::Error; -use tokio::{ - io::AsyncWriteExt, - process::{Child, ChildStderr, ChildStdin, ChildStdout, Command}, - select, -}; -use tokio_stream::StreamExt; -use tokio_util::codec::{self, FramedRead, LinesCodec}; -lazy_static! { - static ref GROUP_RE: Regex = - Regex::new(r#"".*?""#).expect("Regex must compile (fail with GROUP_RE)"); - static ref QUOTE_RE: Regex = - Regex::new(r#"""#).expect("Regex must compile (fail with QUOTE_RE)"); - static ref ESC_RE: Regex = Regex::new(r"\\\s").expect("Regex must compile (fail with ESC_RE)"); -} - -#[derive(Error, Debug)] -pub enum ProcessError { - #[error("{0}")] - Setup(String), - #[error("Unrecoverable process error: {0}")] - Unrecoverable(String), -} - -pub struct ProcessSource { - process: Child, - buffer: DeqBuffer, - stdout: FramedRead, - stderr: FramedRead, - stdin: ChildStdin, -} - -impl Drop for ProcessSource { - fn drop(&mut self) { - let is_process_alive = self.process.try_wait().is_ok_and(|state| state.is_none()); - if is_process_alive { - if let Err(err) = self.process.start_kill() { - warn!("Fail to kill child process: {err}"); - } - } - } -} - -impl ProcessSource { - pub fn parse_command(command: &str) -> Result, ProcessError> { - let mut groups: Vec = vec![]; - let parsed = ESC_RE.replace_all(command, "==esc_space==").to_string(); - let parsed = GROUP_RE.replace_all(&parsed, |caps: &Captures| { - let index = groups.len(); - if caps.len() != 0 { - let group = caps[0].to_string(); - groups.push(QUOTE_RE.replace_all(&group, "").to_string()); - } - format!("==extraction:({index})==") - }); - Ok(parsed - .split(' ') - .map(|a| { - let mut str = a.to_string(); - for (i, g) in groups.iter().enumerate() { - let key = format!("==extraction:({i})=="); - str.replace(&key, g).clone_into(&mut str); - } - let restored = str.replace("==esc_space==", " "); - OsString::from(tilde(&restored).to_string()) - }) - .collect()) - } - - #[cfg(windows)] - fn spawn( - cmd: OsString, - args: Vec, - cwd: PathBuf, - envs: HashMap, - ) -> Result { - const CREATE_NO_WINDOW: u32 = 0x08000000; - Command::new(cmd) - .args(args) - .current_dir(OsString::from(cwd)) - .envs(envs) - .creation_flags(CREATE_NO_WINDOW) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .stdin(Stdio::piped()) - .kill_on_drop(true) - .spawn() - .map_err(|e| ProcessError::Setup(format!("{e}"))) - } - - #[cfg(not(windows))] - fn spawn( - cmd: OsString, - args: Vec, - cwd: PathBuf, - envs: HashMap, - ) -> Result { - Command::new(cmd) - .args(args) - .current_dir(OsString::from(cwd)) - .envs(envs) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .stdin(Stdio::piped()) - .kill_on_drop(true) - .spawn() - .map_err(|e| ProcessError::Setup(format!("{e}"))) - } - - pub async fn new( - command: String, - cwd: PathBuf, - envs: HashMap, - ) -> Result { - let mut args = ProcessSource::parse_command(&command)?; - let cmd = if args.is_empty() { - return Err(ProcessError::Setup(format!( - "Not command has been found in \"{command}\"" - ))); - } else { - args.remove(0) - }; - let mut process = ProcessSource::spawn(cmd, args, cwd, envs)?; - let stdout = codec::FramedRead::new( - process - .stdout - .take() - .ok_or_else(|| ProcessError::Setup(String::from("Fail to get stdout handle")))?, - LinesCodec::default(), - ); - let stderr = codec::FramedRead::new( - process - .stderr - .take() - .ok_or_else(|| ProcessError::Setup(String::from("Fail to get stderr handle")))?, - LinesCodec::default(), - ); - let stdin = process - .stdin - .take() - .ok_or_else(|| ProcessError::Setup(String::from("Fail to get stdin handle")))?; - Ok(Self { - process, - buffer: DeqBuffer::new(8192), - stdout, - stderr, - stdin, - }) - } -} - -impl ByteSource for ProcessSource { - async fn load( - &mut self, - _filter: Option<&SourceFilter>, - ) -> Result, SourceError> { - let mut closing = false; - let mut output; - // Implementation is cancel-safe here because there is no data gathered between to await - // calls. The only multiple await calls here, are actually while closing where there is - // no data to lose anyway. - loop { - if !closing { - output = select! { - res = self.stdout.next() => res, - res = self.stderr.next() => { - if res.is_none() { - closing = true; - } - res - }, - }; - if !closing { - break; - } - } else { - output = self.stdout.next().await; - break; - } - } - if let Some(Ok(line)) = output { - let stored = line.len() + 1; - self.buffer.write_from(line.as_bytes()); - self.buffer.write_from(b"\n"); - let available_bytes = self.buffer.read_available(); - Ok(Some(ReloadInfo::new(stored, available_bytes, 0, None))) - } else if let Some(Err(err)) = output { - Err(SourceError::Unrecoverable(format!("{err}"))) - } else { - Ok(None) - } - } - - fn current_slice(&self) -> &[u8] { - self.buffer.read_slice() - } - - fn consume(&mut self, offset: usize) { - self.buffer.read_done(offset); - } - - fn len(&self) -> usize { - self.buffer.read_available() - } - - fn is_empty(&self) -> bool { - self.len() == 0 - } - - async fn income( - &mut self, - request: stypes::SdeRequest, - ) -> Result { - let bytes = match request { - stypes::SdeRequest::WriteText(ref str) => str.as_bytes(), - stypes::SdeRequest::WriteBytes(ref bytes) => bytes, - }; - self.stdin.write_all(bytes).await.map_err(SourceError::Io)?; - Ok(stypes::SdeResponse { bytes: bytes.len() }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::general_source_reload_test; - - #[tokio::test] - async fn test_process() -> Result<(), ProcessError> { - use std::env; - let mut command = ""; - if cfg!(windows) { - command = "help"; - } else if cfg!(unix) { - command = "ls -lsa"; - } - let envs = HashMap::new(); - match ProcessSource::new(command.to_string(), env::current_dir().unwrap(), envs).await { - Ok(mut process_source) => { - while process_source - .load(None) - .await - .expect("Reload data from process source failed") - .is_some() - { - assert!(!process_source.current_slice().is_empty()); - process_source.consume(process_source.current_slice().len()); - } - // By some reasons during test sometimes process stay alive and as result - let _ = process_source.process.kill().await; - Ok(()) - } - Err(err) => Err(err), - } - } - - #[tokio::test] - async fn test_parsing() -> Result<(), ProcessError> { - let parsed = - ProcessSource::parse_command(r#"cmd arg2 "some_path/with space or spaces" arg3"#)?; - assert_eq!(parsed.len(), 4); - assert_eq!(parsed[0], OsString::from("cmd")); - assert_eq!(parsed[1], OsString::from("arg2")); - assert_eq!(parsed[2], OsString::from("some_path/with space or spaces")); - assert_eq!(parsed[3], OsString::from("arg3")); - let parsed = - ProcessSource::parse_command(r"cmd arg2 some_path/with\ space\ or\ spaces arg3")?; - assert_eq!(parsed.len(), 4); - assert_eq!(parsed[0], OsString::from("cmd")); - assert_eq!(parsed[1], OsString::from("arg2")); - assert_eq!(parsed[2], OsString::from("some_path/with space or spaces")); - assert_eq!(parsed[3], OsString::from("arg3")); - Ok(()) - } - - #[tokio::test] - async fn test_source_reload() { - use std::env; - let mut command = ""; - if cfg!(windows) { - command = "help"; - } else if cfg!(unix) { - command = "ls -lsa"; - } - let envs = HashMap::new(); - let mut process_source = - ProcessSource::new(command.to_string(), env::current_dir().unwrap(), envs) - .await - .unwrap(); - - general_source_reload_test(&mut process_source).await; - } -} diff --git a/application/apps/indexer/sources/src/lib.rs b/application/apps/indexer/sources/src/lib.rs index 9242003d1..72418f6d9 100644 --- a/application/apps/indexer/sources/src/lib.rs +++ b/application/apps/indexer/sources/src/lib.rs @@ -1,147 +1,108 @@ +pub mod api; +#[cfg(test)] +pub mod tests; +pub use api::*; + // Rust can't currently distinguish between dev and none-dev dependencies at the moment. There is // an open issue for this case: "https://github.com/rust-lang/rust/issues/129637" - -use thiserror::Error; - #[macro_use] extern crate lazy_static; #[macro_use] extern crate log; -#[cfg(test)] -mod tests; - pub mod binary; pub mod command; -pub mod producer; -pub mod sde; +pub mod prelude; pub mod serial; pub mod socket; -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum TransportProtocol { - TCP, - UDP, - Unknown, +pub enum Sources { + Raw(binary::raw::BinaryByteSourceFromFile), + Pcap(binary::pcap::legacy::PcapLegacyByteSourceFromFile), + PcapNg(binary::pcap::ng::PcapngByteSourceFromFile), + Tcp(socket::tcp::TcpSource), + Udp(socket::udp::UdpSource), + Serial(serial::serialport::SerialSource), + Process(command::ProcessSource), } -impl From> for TransportProtocol { - fn from(tp_slice: etherparse::TransportSlice<'_>) -> Self { - match tp_slice { - etherparse::TransportSlice::Tcp(_) => TransportProtocol::TCP, - etherparse::TransportSlice::Udp(_) => TransportProtocol::UDP, - _ => TransportProtocol::Unknown, +impl ByteSource for Sources { + fn consume(&mut self, offset: usize) { + match self { + Self::Raw(inner) => inner.consume(offset), + Self::Pcap(inner) => inner.consume(offset), + Self::PcapNg(inner) => inner.consume(offset), + Self::Tcp(inner) => inner.consume(offset), + Self::Udp(inner) => inner.consume(offset), + Self::Serial(inner) => inner.consume(offset), + Self::Process(inner) => inner.consume(offset), } } -} - -#[derive(Debug)] -pub struct SourceFilter { - transport: Option, -} -#[derive(Debug)] -pub struct ReloadInfo { - pub newly_loaded_bytes: usize, - pub available_bytes: usize, - pub skipped_bytes: usize, - pub last_known_ts: Option, -} - -impl ReloadInfo { - pub fn new( - newly_loaded_bytes: usize, - available_bytes: usize, - skipped_bytes: usize, - last_known_ts: Option, - ) -> Self { - Self { - newly_loaded_bytes, - available_bytes, - skipped_bytes, - last_known_ts, + fn current_slice(&self) -> &[u8] { + match self { + Self::Raw(inner) => inner.current_slice(), + Self::Pcap(inner) => inner.current_slice(), + Self::PcapNg(inner) => inner.current_slice(), + Self::Tcp(inner) => inner.current_slice(), + Self::Udp(inner) => inner.current_slice(), + Self::Serial(inner) => inner.current_slice(), + Self::Process(inner) => inner.current_slice(), } } -} -#[derive(Error, Debug)] -pub enum Error { - #[error("Sources setup problem: {0}")] - Setup(String), - #[error("Unrecoverable source error: {0}")] - Unrecoverable(String), - #[error("IO error: {0}")] - Io(std::io::Error), - #[error("Not supported feature")] - NotSupported, -} - -impl From for stypes::NativeError { - fn from(err: Error) -> Self { - stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::ComputationFailed, - message: Some(format!("Fail create source: {err}")), + fn len(&self) -> usize { + match self { + Self::Raw(inner) => inner.len(), + Self::Pcap(inner) => inner.len(), + Self::PcapNg(inner) => inner.len(), + Self::Tcp(inner) => inner.len(), + Self::Udp(inner) => inner.len(), + Self::Serial(inner) => inner.len(), + Self::Process(inner) => inner.len(), } } -} - -pub(crate) const DEFAULT_READER_CAPACITY: usize = 10 * 1024 * 1024; -pub(crate) const DEFAULT_MIN_BUFFER_SPACE: usize = 10 * 1024; - -// Warning can be suppressed here because we are using this trait in our own codebase only. -#[allow(async_fn_in_trait)] -/// A `ByteSource` provides a way to read data from some underlying data source. But it does -/// not provide a simple read interface, rather it allows implementations to filter the data -/// while reading it from it's underlying source. -/// A good example is a network trace where complete ethernet frames are described. If we only -/// want to extract the data part from certain frames, the `relaod` method will load only the relevant -/// data into an internal buffer. -/// This data can then be accessed via the `current_slice` method. -pub trait ByteSource: Send { - /// Indicate that we have consumed a certain amount of data from our internal - /// buffer and that this part can be discarded - fn consume(&mut self, offset: usize); - - /// Provide access to the filtered data that is currently loaded - fn current_slice(&self) -> &[u8]; - /// count of currently loaded bytes - fn len(&self) -> usize; - - fn is_empty(&self) -> bool { - self.len() == 0 + async fn load( + &mut self, + filter: Option<&SourceFilter>, + ) -> Result, SourceError> { + match self { + Self::Raw(inner) => inner.load(filter).await, + Self::Pcap(inner) => inner.load(filter).await, + Self::PcapNg(inner) => inner.load(filter).await, + Self::Tcp(inner) => inner.load(filter).await, + Self::Udp(inner) => inner.load(filter).await, + Self::Serial(inner) => inner.load(filter).await, + Self::Process(inner) => inner.load(filter).await, + } } - /// will load more bytes from the underlying source - /// when the source has reached it's end, this function - /// will return Ok((None, _)) - /// - /// A successful reload operation will return the number - /// of bytes that were newly loaded `newly_loaded_bytes` - /// along with all currently available bytes `available_bytes` - /// In some cases it is possible that some bytes had to be skipped in order to - /// reach the next usable bytes, this is indicated in the `skipped_bytes` number - /// - /// - /// If the source has access to some timestamp (e.g. timestamp of network package), - /// this timestamp is passed on additionally (`last_known_ts`) - /// - /// # Note: - /// - /// This function must be **Cancel-Safe** - async fn load(&mut self, filter: Option<&SourceFilter>) -> Result, Error>; - - /// In case the ByteSource is some kind of connection that does not end, - /// cancel can be implemented that will give the ByteSource the chance to perform some - /// cleanup before the ByteSource is discarded - async fn cancel(&mut self) -> Result<(), Error> { - Ok(()) + async fn cancel(&mut self) -> Result<(), SourceError> { + match self { + Self::Raw(inner) => inner.cancel().await, + Self::Pcap(inner) => inner.cancel().await, + Self::PcapNg(inner) => inner.cancel().await, + Self::Tcp(inner) => inner.cancel().await, + Self::Udp(inner) => inner.cancel().await, + Self::Serial(inner) => inner.cancel().await, + Self::Process(inner) => inner.cancel().await, + } } - /// Append incoming (SDE) Source-Data-Exchange to the data. - async fn income(&mut self, _msg: stypes::SdeRequest) -> Result { - Err(Error::NotSupported) + async fn income( + &mut self, + msg: stypes::SdeRequest, + ) -> Result { + match self { + Self::Raw(inner) => inner.income(msg).await, + Self::Pcap(inner) => inner.income(msg).await, + Self::PcapNg(inner) => inner.income(msg).await, + Self::Tcp(inner) => inner.income(msg).await, + Self::Udp(inner) => inner.income(msg).await, + Self::Serial(inner) => inner.income(msg).await, + Self::Process(inner) => inner.income(msg).await, + } } } diff --git a/application/apps/indexer/sources/src/prelude.rs b/application/apps/indexer/sources/src/prelude.rs new file mode 100644 index 000000000..7568d8b13 --- /dev/null +++ b/application/apps/indexer/sources/src/prelude.rs @@ -0,0 +1,9 @@ +pub use crate::{ + binary::{ + pcap::{legacy::PcapLegacyByteSource, ng::PcapngByteSource}, + raw::BinaryByteSource, + }, + command::ProcessSource, + serial::serialport::SerialSource, + socket::{tcp::TcpSource, udp::UdpSource}, +}; diff --git a/application/apps/indexer/sources/src/serial/descriptor.rs b/application/apps/indexer/sources/src/serial/descriptor.rs new file mode 100644 index 000000000..8b4d6203f --- /dev/null +++ b/application/apps/indexer/sources/src/serial/descriptor.rs @@ -0,0 +1,260 @@ +use crate::prelude::SerialSource; +use crate::*; +use descriptor::{ + CommonDescriptor, FieldsResult, LazyFieldsTask, SourceDescriptor, SourceFactory, + StaticFieldResult, +}; +use std::{collections::HashMap, str}; +use stypes::{ + ExtractByKey, Extracted, Field, FieldDesc, LazyFieldDesc, NativeError, NativeErrorKind, + SessionAction, Severity, StaticFieldDesc, ValueInput, missed_field_err as missed, +}; + +use super::serialport::SerialConfig; + +const SERIAL_SOURCE_UUID: uuid::Uuid = uuid::Uuid::from_bytes([ + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, +]); + +/// The path to the serial port. +const FIELD_PATH: &str = "SERIAL_SOURCE_FIELD_PATH"; +/// The baud rate for the connection. +const FIELD_BAUD_RATE: &str = "SERIAL_SOURCE_FIELD_BAUD_RATE"; +/// The number of data bits per frame. +const FIELD_DATA_BITS: &str = "SERIAL_SOURCE_FIELD_DATA_BITS"; +/// The flow control setting. +const FIELD_FLOW_CONTROL: &str = "SERIAL_SOURCE_FIELD_FLOW_CONTROL"; +/// The parity setting. +const FIELD_PARITY: &str = "SERIAL_SOURCE_FIELD_PARITY"; +/// The number of stop bits. +const FIELD_STOP_BITS: &str = "SERIAL_SOURCE_FIELD_STOP_BITS"; +/// The delay in sending data, in milliseconds. +const FIELD_SEND_DATA_DELAY: &str = "SERIAL_SOURCE_FIELD_SEND_DATA_DELAY"; +/// Whether the connection is exclusive. +const FIELD_EXCLUSIVE: &str = "SERIAL_SOURCE_FIELD_EXCLUSIVE"; +/// List of ports +const FIELD_PORTS_LIST: &str = "SERIAL_SOURCE_PORTS_LIST_FIELD"; + +#[derive(Default)] +pub struct Descriptor {} + +impl SourceFactory for Descriptor { + fn create( + &self, + origin: &stypes::SessionAction, + options: &[stypes::Field], + ) -> Result)>, stypes::NativeError> { + let errors = self.validate(origin, options)?; + if !errors.is_empty() { + return Err(NativeError { + kind: NativeErrorKind::Configuration, + severity: Severity::ERROR, + message: Some( + errors + .values() + .map(String::as_str) + .collect::>() + .join("; "), + ), + }); + } + let path: String = options + .extract_by_key(FIELD_PATH) + .ok_or(missed(FIELD_PATH))? + .value; + let config = SerialConfig { + path: path.clone(), + baud_rate: options + .extract_by_key(FIELD_BAUD_RATE) + .ok_or(missed(FIELD_BAUD_RATE))? + .value, + data_bits: options + .extract_by_key(FIELD_DATA_BITS) + .ok_or(missed(FIELD_DATA_BITS))? + .value, + flow_control: options + .extract_by_key(FIELD_FLOW_CONTROL) + .ok_or(missed(FIELD_FLOW_CONTROL))? + .value, + parity: options + .extract_by_key(FIELD_PARITY) + .ok_or(missed(FIELD_PARITY))? + .value, + stop_bits: options + .extract_by_key(FIELD_STOP_BITS) + .ok_or(missed(FIELD_STOP_BITS))? + .value, + send_data_delay: options + .extract_by_key(FIELD_SEND_DATA_DELAY) + .ok_or(missed(FIELD_SEND_DATA_DELAY))? + .value, + exclusive: options + .extract_by_key(FIELD_EXCLUSIVE) + .ok_or(missed(FIELD_EXCLUSIVE))? + .value, + }; + Ok(Some(( + Sources::Serial(SerialSource::new(config)?), + Some(path), + ))) + } +} + +impl CommonDescriptor for Descriptor { + fn is_compatible(&self, origin: &SessionAction) -> bool { + match origin { + SessionAction::File(..) | SessionAction::Files(..) | SessionAction::ExportRaw(..) => { + false + } + SessionAction::Source => true, + } + } + fn ident(&self) -> stypes::Ident { + stypes::Ident { + name: String::from("Serial Port Connection"), + desc: String::from( + "Connects to the specified serial port using user-defined settings. Data is received as a raw byte stream in a \"as-is\" mode - that is, the parser receives exactly what was read from the port, without any framing.", + ), + io: stypes::IODataType::PlaitText, + uuid: SERIAL_SOURCE_UUID, + } + } + fn fields_getter(&self, _origin: &SessionAction) -> FieldsResult { + Ok(vec![ + FieldDesc::Static(StaticFieldDesc { + id: FIELD_PATH.to_owned(), + name: "Path to dev".to_owned(), + desc: String::new(), + required: true, + interface: ValueInput::String(String::new(), "/dev/tty01".to_owned()), + binding: None, + }), + FieldDesc::Lazy(LazyFieldDesc { + id: FIELD_PORTS_LIST.to_string(), + name: "Ports".to_string(), + desc: String::new(), + binding: Some(FIELD_PATH.to_string()), + }), + FieldDesc::Static(StaticFieldDesc { + id: FIELD_BAUD_RATE.to_owned(), + name: "Boud rate".to_owned(), + desc: String::new(), + required: true, + interface: ValueInput::Numbers( + vec![ + 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, + 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000, + 1152000, 1500000, 2000000, 2500000, 3000000, 3500000, 4000000, + ], + 115200, + ), + binding: None, + }), + FieldDesc::Static(StaticFieldDesc { + id: FIELD_DATA_BITS.to_owned(), + name: "Data bits".to_owned(), + desc: String::new(), + required: true, + interface: ValueInput::Numbers(vec![8, 7, 6, 5], 8), + binding: None, + }), + FieldDesc::Static(StaticFieldDesc { + id: FIELD_STOP_BITS.to_owned(), + name: "Stop bits".to_owned(), + desc: String::new(), + required: true, + interface: ValueInput::Numbers(vec![1, 2], 1), + binding: None, + }), + FieldDesc::Static(StaticFieldDesc { + id: FIELD_FLOW_CONTROL.to_owned(), + name: "Flow control".to_owned(), + desc: String::new(), + required: true, + interface: ValueInput::Strings( + vec![ + String::from("None"), + String::from("Hardware"), + String::from("Software"), + ], + String::from("None"), + ), + binding: None, + }), + FieldDesc::Static(StaticFieldDesc { + id: FIELD_PARITY.to_owned(), + name: "Parity".to_owned(), + desc: String::new(), + required: true, + interface: ValueInput::Strings( + vec![ + String::from("None"), + String::from("Odd"), + String::from("Even"), + ], + String::from("None"), + ), + binding: None, + }), + FieldDesc::Static(StaticFieldDesc { + id: FIELD_EXCLUSIVE.to_owned(), + name: "Exclusive".to_owned(), + desc: String::new(), + required: true, + interface: ValueInput::NamedBools(vec![ + (String::from("Yes"), true), + (String::from("No"), false), + ]), + binding: None, + }), + FieldDesc::Static(StaticFieldDesc { + id: FIELD_SEND_DATA_DELAY.to_owned(), + name: "Send delay, ms".to_owned(), + desc: String::new(), + required: true, + interface: ValueInput::Numbers(vec![0, 10, 20, 30, 40, 50], 0), + binding: None, + }), + ]) + } + fn lazy_fields_getter( + &self, + _origin: SessionAction, + _cancel: tokio_util::sync::CancellationToken, + ) -> LazyFieldsTask { + Box::pin(async move { + let ports = serialport::available_ports() + .map_err(|e| stypes::ComputationError::IoOperation(e.to_string()))? + .into_iter() + .map(|p| p.port_name) + .collect::>(); + Ok(vec![StaticFieldResult::Success(StaticFieldDesc { + id: FIELD_PORTS_LIST.to_owned(), + name: "Ports".to_string(), + desc: "List of available ports".to_string(), + required: false, + interface: ValueInput::Strings(ports, String::new()), + binding: Some(FIELD_PATH.to_string()), + })]) + }) + } + fn validate( + &self, + _origin: &SessionAction, + fields: &[Field], + ) -> Result, NativeError> { + let path: Extracted = fields + .extract_by_key(FIELD_PATH) + .ok_or(missed(FIELD_PATH))?; + let mut errors = HashMap::new(); + if path.value.trim().is_empty() { + errors.insert(path.id.to_owned(), "Path cannot be empty".to_owned()); + } + Ok(errors) + } +} +impl SourceDescriptor for Descriptor { + fn is_sde_supported(&self, _origin: &stypes::SessionAction) -> bool { + true + } +} diff --git a/application/apps/indexer/sources/src/serial/mod.rs b/application/apps/indexer/sources/src/serial/mod.rs index d5689fad1..5742c421a 100644 --- a/application/apps/indexer/sources/src/serial/mod.rs +++ b/application/apps/indexer/sources/src/serial/mod.rs @@ -1 +1,2 @@ +pub mod descriptor; pub mod serialport; diff --git a/application/apps/indexer/sources/src/serial/serialport.rs b/application/apps/indexer/sources/src/serial/serialport.rs index 68c79aca4..14cc89940 100644 --- a/application/apps/indexer/sources/src/serial/serialport.rs +++ b/application/apps/indexer/sources/src/serial/serialport.rs @@ -1,4 +1,4 @@ -use crate::{ByteSource, Error as SourceError, ReloadInfo, SourceFilter}; +use crate::*; use bufread::DeqBuffer; use bytes::{BufMut, BytesMut}; use futures::{ @@ -81,15 +81,27 @@ pub struct SerialSource { send_data_delay: u8, } -// Do we need to do some actions of destructor? -// impl Drop for SerialSource { -// fn drop(&mut self) { -// // Todo something good -// } -// } +pub struct SerialConfig { + /// The path to the serial port. + pub path: String, + /// The baud rate for the connection. + pub baud_rate: u32, + /// The number of data bits per frame. + pub data_bits: u8, + /// The flow control setting. + pub flow_control: u8, + /// The parity setting. + pub parity: u8, + /// The number of stop bits. + pub stop_bits: u8, + /// The delay in sending data, in milliseconds. + pub send_data_delay: u8, + /// Whether the connection is exclusive. + pub exclusive: bool, +} impl SerialSource { - pub fn new(config: &stypes::SerialTransportConfig) -> Result { + pub fn new(config: SerialConfig) -> Result { match tokio_serial::new(config.path.as_str(), config.baud_rate) .data_bits(data_bits(&config.data_bits)) .flow_control(flow_control(&config.flow_control)) diff --git a/application/apps/indexer/sources/src/socket/tcp/descriptor.rs b/application/apps/indexer/sources/src/socket/tcp/descriptor.rs new file mode 100644 index 000000000..ecc222fd9 --- /dev/null +++ b/application/apps/indexer/sources/src/socket/tcp/descriptor.rs @@ -0,0 +1,102 @@ +use crate::*; +use descriptor::{CommonDescriptor, FieldsResult, SourceDescriptor, SourceFactory}; +use std::collections::HashMap; +use stypes::{ + ExtractByKey, Extracted, Field, FieldDesc, NativeError, NativeErrorKind, SessionAction, + Severity, StaticFieldDesc, ValueInput, missed_field_err as missed, +}; + +use crate::prelude::TcpSource; + +const TCP_SOURCE_UUID: uuid::Uuid = uuid::Uuid::from_bytes([ + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, +]); + +// TODO: make fields ids more generic, for example prefix can be taken from source trait somehow +const FIELD_IP_ADDR: &str = "TCP_SOURCE_FIELD_IP_ADDR"; + +#[derive(Default)] +pub struct Descriptor {} + +impl SourceFactory for Descriptor { + fn create( + &self, + origin: &stypes::SessionAction, + options: &[stypes::Field], + ) -> Result)>, stypes::NativeError> { + let errors = self.validate(origin, options)?; + if !errors.is_empty() { + return Err(NativeError { + kind: NativeErrorKind::Configuration, + severity: Severity::ERROR, + message: Some( + errors + .values() + .map(String::as_str) + .collect::>() + .join("; "), + ), + }); + } + let addr: String = options + .extract_by_key(FIELD_IP_ADDR) + .ok_or(missed(FIELD_IP_ADDR))? + .value; + Ok(Some(( + Sources::Tcp(TcpSource::new(&addr, None, None)?), + Some(format!("TCP on {addr}")), + ))) + } +} + +impl CommonDescriptor for Descriptor { + fn is_compatible(&self, origin: &SessionAction) -> bool { + match origin { + SessionAction::File(..) | SessionAction::Files(..) | SessionAction::ExportRaw(..) => { + false + } + SessionAction::Source => true, + } + } + fn ident(&self) -> stypes::Ident { + stypes::Ident { + name: String::from("TCP Connection"), + desc: String::from( + "Connects to the specified IP address and port to receive incoming messages. If the connection fails, an appropriate error is returned. Each individual TCP message is passed to the parser without any headers - only the payload is forwarded.", + ), + io: stypes::IODataType::NetworkFramePayload, + uuid: TCP_SOURCE_UUID, + } + } + fn fields_getter(&self, _origin: &SessionAction) -> FieldsResult { + Ok(vec![FieldDesc::Static(StaticFieldDesc { + id: FIELD_IP_ADDR.to_owned(), + name: "IP address and port".to_owned(), + desc: "Specifies the target address of the remote TCP server. The value must include both the IP address and the port, using the format IP:PORT - for example, 192.168.0.100:8888. This field is mandatory, and the parser will attempt to connect to the given endpoint exactly as specified. Use 0.0.0.0:PORT to bind to all local interfaces, if applicable.".to_owned(), + required: true, + interface: ValueInput::String(String::new(), "0.0.0.0:8888".to_owned()), + binding: None, + })]) + } + fn validate( + &self, + _origin: &SessionAction, + fields: &[Field], + ) -> Result, NativeError> { + fn is_valid(addr: &str) -> bool { + addr.parse::().is_ok() + } + let addr: Extracted = fields + .extract_by_key(FIELD_IP_ADDR) + .ok_or(missed(FIELD_IP_ADDR))?; + let mut errors = HashMap::new(); + if !is_valid(&addr.value) { + errors.insert( + addr.id.to_owned(), + "Expecting IP format 0.0.0.0::8888 (port is required)".to_owned(), + ); + } + Ok(errors) + } +} +impl SourceDescriptor for Descriptor {} diff --git a/application/apps/indexer/sources/src/socket/tcp.rs b/application/apps/indexer/sources/src/socket/tcp/mod.rs similarity index 96% rename from application/apps/indexer/sources/src/socket/tcp.rs rename to application/apps/indexer/sources/src/socket/tcp/mod.rs index 6f9e7d849..5655b0d20 100644 --- a/application/apps/indexer/sources/src/socket/tcp.rs +++ b/application/apps/indexer/sources/src/socket/tcp/mod.rs @@ -1,14 +1,15 @@ -use std::{net::SocketAddr, time::Duration}; +mod descriptor; -use crate::{ByteSource, Error as SourceError, ReloadInfo, SourceFilter}; +use super::{BuffCapacityState, MAX_BUFF_SIZE, MAX_DATAGRAM_SIZE, handle_buff_capacity}; +use crate::*; use bufread::DeqBuffer; use reconnect::{ReconnectInfo, ReconnectResult, TcpReconnecter}; use socket2::{SockRef, TcpKeepalive}; -use tokio::net::TcpStream; - -use super::{BuffCapacityState, MAX_BUFF_SIZE, MAX_DATAGRAM_SIZE, handle_buff_capacity}; +use std::{net::SocketAddr, time::Duration}; +use tokio::{net::TcpStream, runtime::Handle}; pub mod reconnect; +pub use descriptor::*; /// Configurations for keep-alive probes in TCP communication. #[derive(Debug, Clone)] @@ -34,13 +35,13 @@ pub struct TcpSource { } impl TcpSource { - pub async fn new( + pub fn new( addr: &str, keepalive: Option, reconnect_info: Option, ) -> Result { let binding_address = addr.parse().map_err(std::io::Error::other)?; - let socket = Self::create_socket(binding_address, keepalive.as_ref()).await?; + let socket = Self::create_socket(binding_address, keepalive.as_ref())?; let reconnecter = reconnect_info.map(|rec| TcpReconnecter::new(rec, binding_address, keepalive)); Ok(Self { @@ -51,11 +52,13 @@ impl TcpSource { }) } - async fn create_socket( + fn create_socket( binding_address: SocketAddr, keep_alive: Option<&KeepAliveConfig>, ) -> std::io::Result { - let socket = TcpStream::connect(binding_address).await?; + let socket = tokio::task::block_in_place(|| { + Handle::current().block_on(TcpStream::connect(binding_address)) + })?; if let Some(keepalive_config) = keep_alive { let socket_ref = SockRef::from(&socket); let keepalive = TcpKeepalive::new() @@ -199,7 +202,7 @@ mod tests { } } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_tcp_reload() -> Result<(), std::io::Error> { static SERVER: &str = "127.0.0.1:4000"; let listener = TcpListener::bind(&SERVER).await.unwrap(); @@ -208,7 +211,7 @@ mod tests { let send_handle = tokio::spawn(async move { accept_and_send(&listener, 100).await; }); - let mut tcp_source = TcpSource::new(SERVER, None, None).await?; + let mut tcp_source = TcpSource::new(SERVER, None, None)?; let receive_handle = tokio::spawn(async move { for msg in MESSAGES { tcp_source.load(None).await.expect("reload failed"); @@ -228,7 +231,7 @@ mod tests { Ok(()) } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_general_source_reload() { static SERVER: &str = "127.0.0.1:4001"; let listener = TcpListener::bind(&SERVER).await.unwrap(); @@ -237,18 +240,17 @@ mod tests { tokio::spawn(async move { accept_and_send(&listener, 100).await; }); - let mut tcp_source = TcpSource::new(SERVER, None, None).await.unwrap(); + let mut tcp_source = TcpSource::new(SERVER, None, None).unwrap(); general_source_reload_test(&mut tcp_source).await; } /// Tests will send packets with fixed lengths while consuming /// half of the sent length, ensuring the source won't break. - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_source_buffer_overflow() { const SERVER: &str = "127.0.0.1:4002"; let listener = TcpListener::bind(&SERVER).await.unwrap(); - const SENT_LEN: usize = MAX_DATAGRAM_SIZE; const CONSUME_LEN: usize = MAX_DATAGRAM_SIZE / 2; @@ -267,8 +269,7 @@ mod tests { } }); - let mut tcp_source = TcpSource::new(SERVER, None, None).await.unwrap(); - + let mut tcp_source = TcpSource::new(SERVER, None, None).unwrap(); while let Ok(Some(info)) = tcp_source.load(None).await { if info.available_bytes == 0 { break; @@ -277,7 +278,7 @@ mod tests { } } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn reconnect_no_msgs() { static SERVER: &str = "127.0.0.1:4003"; let listener = TcpListener::bind(&SERVER).await.unwrap(); @@ -295,7 +296,7 @@ mod tests { // Enable reconnect without configuring state channels. let rec_info = ReconnectInfo::new(1000, Duration::from_millis(20), None); - let mut tcp_source = TcpSource::new(SERVER, None, Some(rec_info)).await.unwrap(); + let mut tcp_source = TcpSource::new(SERVER, None, Some(rec_info)).unwrap(); let receive_handle = tokio::spawn(async move { // Byte source must receive same data twice without errors with active reconnect for _ in 0..2 { @@ -316,7 +317,7 @@ mod tests { assert!(rec_res.is_ok()); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn reconnect_with_state_msgs() { static SERVER: &str = "127.0.0.1:4004"; let listener = TcpListener::bind(&SERVER).await.unwrap(); @@ -336,7 +337,7 @@ mod tests { let rec_info = ReconnectInfo::new(1000, Duration::from_millis(50), Some(state_tx)); - let mut tcp_source = TcpSource::new(SERVER, None, Some(rec_info)).await.unwrap(); + let mut tcp_source = TcpSource::new(SERVER, None, Some(rec_info)).unwrap(); // Tests reconnecting state messages. let reconnect_handler = tokio::spawn(async move { @@ -388,7 +389,7 @@ mod tests { assert!(reconnect_res.is_ok()); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn reconnect_fail() { static SERVER: &str = "127.0.0.1:4005"; let listener = TcpListener::bind(&SERVER).await.unwrap(); @@ -402,7 +403,7 @@ mod tests { const MAX_ATTEMPTS: usize = 7; let rec_info = ReconnectInfo::new(MAX_ATTEMPTS, Duration::from_millis(10), Some(state_tx)); - let mut tcp_source = TcpSource::new(SERVER, None, Some(rec_info)).await.unwrap(); + let mut tcp_source = TcpSource::new(SERVER, None, Some(rec_info)).unwrap(); // Tests reconnecting state messages. // Failed state message with MAX_ATTEMPTS is expected. @@ -446,7 +447,7 @@ mod tests { /// Ensure load and reconnect functions are cancel safe by keep sending notifications /// in rapid interval while calling them. - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn load_reconnect_cancel_safe() { static SERVER: &str = "127.0.0.1:4006"; let listener = TcpListener::bind(&SERVER).await.unwrap(); @@ -466,7 +467,7 @@ mod tests { let (cancel_tx, mut cancel_rx) = tokio::sync::mpsc::channel(32); - let mut tcp_source = TcpSource::new(SERVER, None, Some(rec_info)).await.unwrap(); + let mut tcp_source = TcpSource::new(SERVER, None, Some(rec_info)).unwrap(); let cancel_handle = tokio::spawn(async move { // Keep sending notifications causing load method to be dropped while both @@ -513,7 +514,7 @@ mod tests { /// Ensure load and reconnect functions are cancel safe by keep calling it within a timeout /// function with rapid interval. - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn load_reconnect_cancel_safe_timeout() { static SERVER: &str = "127.0.0.1:4007"; let listener = TcpListener::bind(&SERVER).await.unwrap(); @@ -531,7 +532,7 @@ mod tests { // Enable reconnect without configuring state channels. let rec_info = ReconnectInfo::new(1000, Duration::from_millis(30), None); - let mut tcp_source = TcpSource::new(SERVER, None, Some(rec_info)).await.unwrap(); + let mut tcp_source = TcpSource::new(SERVER, None, Some(rec_info)).unwrap(); let receive_handle = tokio::spawn(async move { // TCP source must receive three messages, reconnect then receive three diff --git a/application/apps/indexer/sources/src/socket/tcp/reconnect.rs b/application/apps/indexer/sources/src/socket/tcp/reconnect.rs index ffe2b35ba..df3004179 100644 --- a/application/apps/indexer/sources/src/socket/tcp/reconnect.rs +++ b/application/apps/indexer/sources/src/socket/tcp/reconnect.rs @@ -119,7 +119,7 @@ async fn reconnect( } log::info!("Reconnecting to TCP server. Attempt: {attempts}"); - match super::TcpSource::create_socket(binding_address, keep_alive.as_ref()).await { + match super::TcpSource::create_socket(binding_address, keep_alive.as_ref()) { Ok(socket) => { if let Some(sender) = &reconnect_info.state_sender { if let Err(err) = sender.send(ReconnectStateMsg::Connected) { diff --git a/application/apps/indexer/sources/src/socket/udp/descriptor.rs b/application/apps/indexer/sources/src/socket/udp/descriptor.rs new file mode 100644 index 000000000..9d987679b --- /dev/null +++ b/application/apps/indexer/sources/src/socket/udp/descriptor.rs @@ -0,0 +1,205 @@ +use super::{MulticastInfo, UdpSource}; +use crate::*; +use descriptor::{CommonDescriptor, FieldsResult, SourceDescriptor, SourceFactory}; +use std::collections::HashMap; +use stypes::{ + ExtractAs, ExtractByKey, Extracted, Field, FieldDesc, NativeError, NativeErrorKind, + SessionAction, Severity, StaticFieldDesc, Value, ValueInput, missed_field_err as missed, +}; + +const UDP_SOURCE_UUID: uuid::Uuid = uuid::Uuid::from_bytes([ + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, +]); + +const FIELD_IP_ADDR: &str = "UDP_SOURCE_FIELD_IP_ADDR"; +const FIELD_MULTICAST_ADDR: &str = "UDP_SOURCE_FIELD_MULTICAST_ADDR"; + +#[derive(Default)] +pub struct Descriptor {} + +impl SourceFactory for Descriptor { + fn create( + &self, + origin: &stypes::SessionAction, + options: &[stypes::Field], + ) -> Result)>, stypes::NativeError> { + let errors = self.validate(origin, options)?; + if !errors.is_empty() { + return Err(NativeError { + kind: NativeErrorKind::Configuration, + severity: Severity::ERROR, + message: Some( + errors + .values() + .map(String::as_str) + .collect::>() + .join("; "), + ), + }); + } + let addr: String = options + .extract_by_key(FIELD_IP_ADDR) + .ok_or(missed(FIELD_IP_ADDR))? + .value; + let multicast: &Vec = options + .extract_by_key(FIELD_MULTICAST_ADDR) + .ok_or(missed(FIELD_MULTICAST_ADDR))? + .value; + let mut multicasts = Vec::new(); + for field in multicast { + let pair: &Vec = field.extract_as().ok_or(missed("Multicast Addr"))?; + if pair.len() != 2 { + return Err(NativeError { + kind: NativeErrorKind::Configuration, + severity: Severity::ERROR, + message: Some("Invalid settings of multicast address".to_owned()), + }); + } + let addr: String = (&pair[0]).extract_as().ok_or(missed("Multicast"))?; + let interface: String = (&pair[1]).extract_as().ok_or(missed("Interface"))?; + multicasts.push(MulticastInfo { + multiaddr: addr + .parse::() + .map_err(|err| NativeError { + kind: NativeErrorKind::Configuration, + severity: Severity::ERROR, + message: Some(format!("Fail to parse IP: {err}")), + })?, + interface: if interface.is_empty() { + None + } else { + Some(interface) + }, + }) + } + Ok(Some(( + Sources::Udp( + UdpSource::new(&addr, multicasts).map_err(|err| NativeError { + kind: NativeErrorKind::Io, + severity: Severity::ERROR, + message: Some(err.to_string()), + })?, + ), + Some(format!("UDP on {addr}")), + ))) + } +} + +impl CommonDescriptor for Descriptor { + fn is_compatible(&self, origin: &SessionAction) -> bool { + match origin { + SessionAction::File(..) | SessionAction::Files(..) | SessionAction::ExportRaw(..) => { + false + } + SessionAction::Source => true, + } + } + fn ident(&self) -> stypes::Ident { + stypes::Ident { + name: String::from("UDP Connection"), + desc: String::from( + "Starts listening on the specified IP address and port, as well as on any user-defined multicast addresses. Each received UDP packet is passed to the parser as-is, excluding headers - only the payload is forwarded.", + ), + io: stypes::IODataType::NetworkFramePayload, + uuid: UDP_SOURCE_UUID, + } + } + fn fields_getter(&self, _origin: &SessionAction) -> FieldsResult { + Ok(vec![ + FieldDesc::Static(StaticFieldDesc { + id: FIELD_IP_ADDR.to_owned(), + name: "IP address and port".to_owned(), + desc: "Specifies the local address and port on which the application should listen for incoming UDP packets. The value must include both the IP address and the port, using the format IP:PORT - for example, 0.0.0.0:9000. This field is mandatory, and determines the network interface and port bound by the listener. Use 0.0.0.0:PORT to listen on all available interfaces.".to_owned(), + required: true, + interface: ValueInput::String(String::new(), "0.0.0.0:8888".to_owned()), + binding: None, + }), + FieldDesc::Static(StaticFieldDesc { + id: FIELD_MULTICAST_ADDR.to_owned(), + name: "Multicast Addresses".to_owned(), + desc: "An optional list of UDP multicast groups to join. Each entry includes the group address (e.g., 239.0.0.1) and the local interface IP (e.g., 192.168.1.10) used for joining. This allows the source to receive packets sent to the specified multicast addresses in addition to those received on the main local socket (IP:PORT). If the list is empty, only unicast or broadcast messages will be received.".to_owned(), + required: true, + interface: ValueInput::FieldsCollection { + elements: vec![ + StaticFieldDesc { + id: String::new(), + name: "Address".to_owned(), + desc: "".to_owned(), + required: true, + interface: ValueInput::String( + String::new(), + "255.255.255.255".to_owned(), + ), + binding: None, + }, + StaticFieldDesc { + id: String::new(), + name: "Interface".to_owned(), + desc: "".to_owned(), + required: true, + interface: ValueInput::String(String::new(), "0.0.0.0".to_owned()), + binding: None, + }, + ], + add_title: "Add Multicast".to_owned(), + }, + binding: None, + }), + ]) + } + + fn validate( + &self, + _origin: &SessionAction, + fields: &[Field], + ) -> Result, NativeError> { + fn is_valid(addr: &str) -> bool { + addr.parse::().is_ok() + } + let multicast: Extracted<&Vec> = fields + .extract_by_key(FIELD_MULTICAST_ADDR) + .ok_or(missed(FIELD_MULTICAST_ADDR))?; + let mut errors = HashMap::new(); + for pair in multicast.value.iter() { + if let Value::Fields(pair) = &pair.value { + if pair.len() != 2 { + errors.insert( + multicast.id.to_owned(), + "Invalid number of mutlicast settings".to_owned(), + ); + break; + } + if let (Value::String(addr), Value::String(interface)) = + (&pair[0].value, &pair[1].value) + { + if !is_valid(addr) { + errors.insert( + pair[0].id.to_owned(), + "Expecting IP format 255.255.255.255".to_owned(), + ); + } + if !interface.is_empty() && !is_valid(interface) { + errors.insert( + pair[1].id.to_owned(), + "Expecting IP format 0.0.0.0".to_owned(), + ); + } + } else { + errors.insert( + multicast.id.to_owned(), + "Invalid values of mutlicast settings".to_owned(), + ); + break; + } + } else { + errors.insert( + multicast.id.to_owned(), + "Invalid mutlicast settings".to_owned(), + ); + break; + } + } + Ok(errors) + } +} +impl SourceDescriptor for Descriptor {} diff --git a/application/apps/indexer/sources/src/socket/udp.rs b/application/apps/indexer/sources/src/socket/udp/mod.rs similarity index 88% rename from application/apps/indexer/sources/src/socket/udp.rs rename to application/apps/indexer/sources/src/socket/udp/mod.rs index 36394949b..59dbbd511 100644 --- a/application/apps/indexer/sources/src/socket/udp.rs +++ b/application/apps/indexer/sources/src/socket/udp/mod.rs @@ -1,15 +1,19 @@ +mod descriptor; + +use super::{MAX_BUFF_SIZE, MAX_DATAGRAM_SIZE}; +use crate::socket::{BuffCapacityState, handle_buff_capacity}; +use crate::*; use bufread::DeqBuffer; use log::trace; use std::net::{IpAddr, Ipv4Addr}; use thiserror::Error; -use tokio::net::{ToSocketAddrs, UdpSocket}; - -use super::{MAX_BUFF_SIZE, MAX_DATAGRAM_SIZE}; -use crate::{ - ByteSource, Error as SourceError, ReloadInfo, SourceFilter, - socket::{BuffCapacityState, handle_buff_capacity}, +use tokio::{ + net::{ToSocketAddrs, UdpSocket}, + runtime::Handle, }; +pub use descriptor::*; + #[derive(Error, Debug)] pub enum UdpSourceError { #[error("IO Error: {0}")] @@ -24,6 +28,11 @@ pub enum UdpSourceError { Config(stypes::NetError), } +pub struct MulticastInfo { + pub multiaddr: IpAddr, + pub interface: Option, +} + pub struct UdpSource { buffer: DeqBuffer, socket: UdpSocket, @@ -31,16 +40,15 @@ pub struct UdpSource { } impl UdpSource { - pub async fn new( + pub fn new( addr: A, - multicast: Vec, + multicast: Vec, ) -> Result { - let socket = UdpSocket::bind(addr).await.map_err(UdpSourceError::Io)?; + let socket = + tokio::task::block_in_place(|| Handle::current().block_on(UdpSocket::bind(addr))) + .map_err(UdpSourceError::Io)?; for multicast_info in &multicast { - let multi_addr = multicast_info - .multicast_addr() - .map_err(UdpSourceError::Config)?; - match multi_addr { + match multicast_info.multiaddr { IpAddr::V4(addr) => { let inter: Ipv4Addr = match multicast_info.interface.as_ref() { Some(s) => s.parse().map_err(UdpSourceError::ParseAddr)?, @@ -141,7 +149,7 @@ mod tests { static MESSAGES: &[&str] = &["one", "two", "three"]; - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_udp_reload() -> Result<(), UdpSourceError> { static SENDER: &str = "127.0.0.1:4000"; static RECEIVER: &str = "127.0.0.1:5000"; @@ -154,7 +162,7 @@ mod tests { .expect("could not send on socket"); } }); - let mut udp_source = UdpSource::new(RECEIVER, vec![]).await?; + let mut udp_source = UdpSource::new(RECEIVER, vec![])?; let receive_handle = tokio::spawn(async move { for msg in MESSAGES { udp_source.load(None).await.unwrap(); @@ -170,7 +178,7 @@ mod tests { Ok(()) } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_general_source_reload() { static SENDER: &str = "127.0.0.1:4001"; static RECEIVER: &str = "127.0.0.1:5001"; @@ -186,7 +194,7 @@ mod tests { .expect("could not send on socket"); } }); - let mut udp_source = UdpSource::new(RECEIVER, vec![]).await.unwrap(); + let mut udp_source = UdpSource::new(RECEIVER, vec![]).unwrap(); general_source_reload_test(&mut udp_source).await; } @@ -197,19 +205,18 @@ mod tests { /// This test demonstrate that parsers which consume the bytes of one result at a /// time while miss parsing the whole bytes when the server isn't sending more data /// even that the buffer has bytes in it. - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_source_buffer_overflow() { + // TODO: fix this test const SENDER: &str = "127.0.0.1:4002"; const RECEIVER: &str = "127.0.0.1:5002"; const SENT_LEN: usize = MAX_DATAGRAM_SIZE; const CONSUME_LEN: usize = MAX_DATAGRAM_SIZE / 2; - let send_socket = UdpSocket::bind(SENDER) .await .map_err(UdpSourceError::Io) .unwrap(); - // Spawn server in background. tokio::spawn(async move { let msg = [b'a'; SENT_LEN]; @@ -225,9 +232,7 @@ mod tests { total_sent += msg.len(); } }); - - let mut udp_source = UdpSource::new(RECEIVER, vec![]).await.unwrap(); - + let mut udp_source = UdpSource::new(RECEIVER, vec![]).unwrap(); while let Ok(Some(info)) = udp_source.load(None).await { if info.newly_loaded_bytes == 0 { println!( diff --git a/application/apps/indexer/sources/src/tests/mod.rs b/application/apps/indexer/sources/src/tests/mod.rs index 200a02de4..7eca18b6a 100644 --- a/application/apps/indexer/sources/src/tests/mod.rs +++ b/application/apps/indexer/sources/src/tests/mod.rs @@ -1,8 +1,7 @@ -pub mod mock_read; +mod mock_read; -pub use mock_read::MockRead; - -use crate::ByteSource; +use crate::api::ByteSource; +pub use mock_read::*; /// Do general tests on the reload returns to insure the [`byte_source`] match the trait signature /// and documentations diff --git a/application/apps/indexer/stypes/Cargo.toml b/application/apps/indexer/stypes/Cargo.toml index e85dcfc1f..2537bb032 100644 --- a/application/apps/indexer/stypes/Cargo.toml +++ b/application/apps/indexer/stypes/Cargo.toml @@ -12,9 +12,10 @@ rustcore = [ "dep:regex", "dep:envvars", "dlt-core/fibex", + "dep:anyhow", + "dep:tokio-util", "dlt-core/statistics", "dlt-core/serialization", - "dep:anyhow", ] nodejs = [ "dep:node-bindgen" @@ -26,13 +27,14 @@ dlt-core = { workspace = true, features = ["fibex", "serialization"] } regex = { workspace = true, optional = true } bincode = "1.3" extend = { path = "../tools/extend"} -uuid = { workspace = true, features = ["serde"] } +uuid = { workspace = true, features = ["serde", "v4", "js"] } tokio = { workspace = true, optional = true } node-bindgen = { git = "https://github.com/infinyon/node-bindgen.git", branch="master", optional = true} thiserror.workspace = true walkdir = { workspace = true, optional = true } envvars = { workspace = true, optional = true } anyhow = { workspace = true, optional = true } +tokio-util = { workspace = true , features = ["full"], optional = true } [dev-dependencies] tokio = { workspace = true } diff --git a/application/apps/indexer/stypes/bindings/callback.ts b/application/apps/indexer/stypes/bindings/callback.ts index 96d5e7d47..d5ad04557 100644 --- a/application/apps/indexer/stypes/bindings/callback.ts +++ b/application/apps/indexer/stypes/bindings/callback.ts @@ -3,6 +3,7 @@ import type { AttachmentInfo } from "./attachment"; import type { FilterMatchList } from "./miscellaneous"; import type { NativeError } from "./error"; import type { Progress } from "./progress"; +import type { SessionDescriptor } from "./observe"; /** * Represents events sent to the client. @@ -21,7 +22,15 @@ stat: Map, } } | { "IndexedMapUpdated": { /** * The number of log entries from search results available for reading. */ -len: number, } } | { "SearchMapUpdated": FilterMatchList | null } | { "SearchValuesUpdated": Map } | { "AttachmentsUpdated": { +len: number, } } | { "SearchMapUpdated": FilterMatchList | null } | { "SessionDescriptor": { +/** + * Uuid of the observe operation, which adds new `SessionDescriptor` + */ +uuid: string, +/** + * Added `SessionDescriptor` + */ +desc: SessionDescriptor, } } | { "SearchValuesUpdated": Map } | { "AttachmentsUpdated": { /** * The size of the attachment in bytes. */ diff --git a/application/apps/indexer/stypes/bindings/miscellaneous.ts b/application/apps/indexer/stypes/bindings/miscellaneous.ts index 59ab592e7..5aa6ad279 100644 --- a/application/apps/indexer/stypes/bindings/miscellaneous.ts +++ b/application/apps/indexer/stypes/bindings/miscellaneous.ts @@ -1,4 +1,5 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SessionDescriptor } from "./observe"; /** * Data about indices (log entry numbers). Used to provide information about @@ -56,7 +57,7 @@ nature: number, }; */ export type GrabbedElementList = Array; -export type MapKeyValue = { [key in string]?: string }; +export type MapKeyValue = Map; /** * Representation of ranges. We cannot use std ranges as soon as no way @@ -94,10 +95,14 @@ export type SourceDefinition = { * The unique identifier of the source. */ id: number, +/** + * Parent observe opeartion Uuid + */ +uuid: string, /** * The user-friendly name of the source for display purposes. */ -alias: string, }; +descriptor: SessionDescriptor, }; /** * A list of data sources. diff --git a/application/apps/indexer/stypes/bindings/observe.ts b/application/apps/indexer/stypes/bindings/observe.ts index 5af7a3d3f..46c13f981 100644 --- a/application/apps/indexer/stypes/bindings/observe.ts +++ b/application/apps/indexer/stypes/bindings/observe.ts @@ -1,39 +1,70 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ComponentOptions } from "./options"; +import type { IODataType } from "./options"; +import type { PluginParserSettings } from "./plugins"; + +export type ComponentDef = { "Source": ComponentOptions } | { "Parser": ComponentOptions }; + /** - * ATTENTION: - * REFERENCE TO `DltFilterConfig` HAS BEEN ADDED MANUALLY - * BECAUSE THIS TYPE IS NOT GENERATED BY `ts_rs`. + * Represents the type of a component within the system. + * + * The component type indicates the general domain of responsibility and + * functional role of the component. It is used to categorize components + * according to their purpose in the data processing pipeline. */ -import { DltFilterConfig } from './dlt'; -import type { PluginParserSettings } from './plugins'; +export type ComponentType = "Parser" | "Source"; -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +export type ComponentsList = { parsers: Array, sources: Array, }; /** * Settings for the DLT parser. */ -export type DltParserSettings = { - /** - * Configuration for filtering DLT messages. - */ - filter_config: DltFilterConfig; - /** - * Paths to FIBEX files for additional interpretation of `payload` content. - */ - fibex_file_paths: Array | null; - /** - * Indicates whether the source contains a `StorageHeader`. Set to `true` if applicable. - */ - with_storage_header: boolean; - /** - * Timezone for timestamp adjustment. If specified, timestamps are converted to this timezone. - */ - tz: string | null; -}; +export type DltParserSettings = { +/** + * Configuration for filtering DLT messages. + */ +filter_config: DltFilterConfig, +/** + * Paths to FIBEX files for additional interpretation of `payload` content. + */ +fibex_file_paths: Array | null, +/** + * Indicates whether the source contains a `StorageHeader`. Set to `true` if applicable. + */ +with_storage_header: boolean, +/** + * Timezone for timestamp adjustment. If specified, timestamps are converted to this timezone. + */ +tz: string | null, }; /** * Supported file formats for observation. */ -export type FileFormat = 'PcapNG' | 'PcapLegacy' | 'Text' | 'Binary'; +export type FileFormat = "PcapNG" | "PcapLegacy" | "Text" | "Binary"; + +export type Ident = { +/** + * A short, human-readable name of the component (parser/source), independent of context. + * Used on the client side to display available components. + */ +name: string, +/** + * A more detailed description of the component. This information is displayed in a separate + * area and can contain an extended explanation. + */ +desc: string, +/** + * The type of data the component produces or expects. + * For sources, this is the type of data they emit. + * For parsers, this is the type of data they expect as input. + */ +io: IODataType, +/** + * A unique identifier of the component. + */ +uuid: string, }; + +export type IdentList = Array; /** * Multicast configuration information. @@ -41,144 +72,151 @@ export type FileFormat = 'PcapNG' | 'PcapLegacy' | 'Text' | 'Binary'; * - `interface`: The address of the local interface used to join the multicast group. * If set to `INADDR_ANY`, the system selects an appropriate interface. */ -export type MulticastInfo = { multiaddr: string; interface: string | null }; +export type MulticastInfo = { multiaddr: string, interface: string | null, }; /** * Options for observing data within a session. */ -export type ObserveOptions = { - /** - * The description of the data source. - */ - origin: ObserveOrigin; - /** - * The parser configuration to be applied. - */ - parser: ParserType; -}; +export type ObserveOptions = { +/** + * The description of the data source. + */ +origin: ObserveOrigin, +/** + * The parser configuration to be applied. + */ +parser: ParserType, }; /** * Describes the source of data for observation. */ -export type ObserveOrigin = - | { File: [string, FileFormat, string] } - | { Concat: Array<[string, FileFormat, string]> } - | { Stream: [string, Transport] }; +export type ObserveOrigin = { "File": [string, FileFormat, string] } | { "Concat": Array<[string, FileFormat, string]> } | { "Stream": [string, Transport] }; /** * Specifies the parser to be used for processing session data. */ -export type ParserType = - | { Dlt: DltParserSettings } - | { SomeIp: SomeIpParserSettings } - | { Text: null } - | { Plugin: PluginParserSettings }; +export type ParserType = { "Dlt": DltParserSettings } | { "SomeIp": SomeIpParserSettings } | { "Text": null } | { "Plugin": PluginParserSettings }; /** * Configuration for executing terminal commands. */ -export type ProcessTransportConfig = { - /** - * The working directory for the command. - */ - cwd: string; - /** - * The command to execute. - */ - command: string; - /** - * Environment variables. If empty, the default environment variables are used. - */ - envs: Map; -}; +export type ProcessTransportConfig = { +/** + * The working directory for the command. + */ +cwd: string, +/** + * The command to execute. + */ +command: string, +/** + * Environment variables. If empty, the default environment variables are used. + */ +envs: Map, }; /** * Configuration for serial port connections. */ -export type SerialTransportConfig = { - /** - * The path to the serial port. - */ - path: string; - /** - * The baud rate for the connection. - */ - baud_rate: number; - /** - * The number of data bits per frame. - */ - data_bits: number; - /** - * The flow control setting. - */ - flow_control: number; - /** - * The parity setting. - */ - parity: number; - /** - * The number of stop bits. - */ - stop_bits: number; - /** - * The delay in sending data, in milliseconds. - */ - send_data_delay: number; - /** - * Whether the connection is exclusive. - */ - exclusive: boolean; -}; +export type SerialTransportConfig = { +/** + * The path to the serial port. + */ +path: string, +/** + * The baud rate for the connection. + */ +baud_rate: number, +/** + * The number of data bits per frame. + */ +data_bits: number, +/** + * The flow control setting. + */ +flow_control: number, +/** + * The parity setting. + */ +parity: number, +/** + * The number of stop bits. + */ +stop_bits: number, +/** + * The delay in sending data, in milliseconds. + */ +send_data_delay: number, +/** + * Whether the connection is exclusive. + */ +exclusive: boolean, }; + +/** + * Described user basic action. Source means, user is selecting custom source & parser + */ +export type SessionAction = { "File": string } | { "Files": Array } | { "ExportRaw": [Array, Array<{ start: bigint, end: bigint, }>, string] } | "Source"; + +export type SessionDescriptor = { +/** + * Identifier of the parser being used. Provides a general description of the parser. + */ +parser: Ident, +/** + * Identifier of the source being used. Provides a general description of the source. + */ +source: Ident, +/** + * Parser description in the context of the current session. + */ +p_desc: string | null, +/** + * Source description in the context of the current session. For example, instead of "UDP", it might be "UDP on 0.0.0.0::8888". + */ +s_desc: string | null, }; + +export type SessionSetup = { origin: SessionAction, parser: ComponentOptions, source: ComponentOptions, }; /** * Settings for the SomeIp parser. */ -export type SomeIpParserSettings = { - /** - * Paths to FIBEX files for additional interpretation of `payload` content. - */ - fibex_file_paths: Array | null; -}; +export type SomeIpParserSettings = { +/** + * Paths to FIBEX files for additional interpretation of `payload` content. + */ +fibex_file_paths: Array | null, }; /** * Configuration for TCP connections. */ -export type TCPTransportConfig = { - /** - * The address to bind the TCP connection to. - */ - bind_addr: string; -}; +export type TCPTransportConfig = { +/** + * The address to bind the TCP connection to. + */ +bind_addr: string, }; /** * Describes the transport source for a session. */ -export type Transport = - | { Process: ProcessTransportConfig } - | { TCP: TCPTransportConfig } - | { UDP: UDPTransportConfig } - | { Serial: SerialTransportConfig }; +export type Transport = { "Process": ProcessTransportConfig } | { "TCP": TCPTransportConfig } | { "UDP": UDPTransportConfig } | { "Serial": SerialTransportConfig }; /** * Configuration for UDP connections. */ -export type UDPTransportConfig = { - /** - * The address to bind the UDP connection to. - */ - bind_addr: string; - /** - * A list of multicast configurations. - */ - multicast: Array; -}; +export type UDPTransportConfig = { +/** + * The address to bind the UDP connection to. + */ +bind_addr: string, +/** + * A list of multicast configurations. + */ +multicast: Array, }; /** * Configuration for UDP connections. */ -export type UdpConnectionInfo = { - /** - * A list of multicast addresses to listen on. - */ - multicast_addr: Array; -}; +export type UdpConnectionInfo = { +/** + * A list of multicast addresses to listen on. + */ +multicast_addr: Array, }; diff --git a/application/apps/indexer/stypes/bindings/options.ts b/application/apps/indexer/stypes/bindings/options.ts new file mode 100644 index 000000000..a81f5c364 --- /dev/null +++ b/application/apps/indexer/stypes/bindings/options.ts @@ -0,0 +1,32 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Represents events sent to the client. + */ +export type CallbackOptionsEvent = { "LoadingDone": { owner: string, fields: Array, } } | { "LoadingErrors": { owner: string, errors: Array, } } | { "LoadingError": { owner: string, error: string, fields: Array, } } | { "LoadingCancelled": { owner: string, fields: Array, } } | "Destroyed"; + +export type ComponentOptions = { fields: Array, uuid: string, }; + +export type ComponentsOptionsList = { options: Map, }; + +export type Field = { id: string, value: Value, }; + +export type FieldDesc = { "Static": StaticFieldDesc } | { "Lazy": LazyFieldDesc }; + +export type FieldList = Array; + +export type FieldLoadingError = { id: string, err: string, }; + +export type FieldsValidationErrors = { errors: Map, }; + +export type IODataType = "PlaitText" | "Raw" | "NetworkFramePayload" | { "Multiple": Array } | "Any"; + +export type LazyFieldDesc = { id: string, name: string, desc: string, binding: string | null, }; + +export type OutputRender = { "Columns": Array<[string, number]> } | "PlaitText"; + +export type StaticFieldDesc = { id: string, name: string, desc: string, required: boolean, interface: ValueInput, binding: string | null, }; + +export type Value = { "Boolean": boolean } | { "Number": number } | { "Numbers": Array } | { "String": string } | { "Strings": Array } | { "Directories": Array } | { "Files": Array } | { "File": string } | { "Directory": string } | { "KeyNumber": Map } | { "KeyNumbers": Map } | { "KeyString": Map } | { "KeyStrings": Map } | { "Fields": Array }; + +export type ValueInput = { "Checkbox": boolean } | { "Number": number } | { "String": [string, string] } | { "Numbers": [Array, number] } | { "Strings": [Array, string] } | { "NamedBools": Array<[string, boolean]> } | { "NamedNumbers": [Array<[string, number]>, number] } | { "NamedStrings": [Array<[string, string]>, string] } | { "KeyNumber": Map } | { "KeyNumbers": Map } | { "KeyString": Map } | { "KeyStrings": Map } | { "NestedNumbersMap": [Map>>, Map] } | { "NestedStringsMap": [Map>>, Map] } | "Directories" | { "Files": Array } | { "File": Array } | { "Directory": string | null } | "Timezone" | { "InputsCollection": { elements: Array, add_title: string, } } | { "FieldsCollection": { elements: Array, add_title: string, } } | { "Bound": { output: ValueInput, inputs: Array, } }; diff --git a/application/apps/indexer/stypes/bindings/plugins.ts b/application/apps/indexer/stypes/bindings/plugins.ts index fdd9107ec..650b6b2a2 100644 --- a/application/apps/indexer/stypes/bindings/plugins.ts +++ b/application/apps/indexer/stypes/bindings/plugins.ts @@ -3,54 +3,51 @@ /** * Represents the infos of a column that will be used in the render options. */ -export type ColumnInfo = { - /** - * Header title to be rendered on the top of the column in log view. - */ - caption: string; - /** - * Description to be shown as tooltip for the column. - */ - description: string; - /** - * Width of column (-1) for unlimited. - */ - width: number; -}; +export type ColumnInfo = { +/** + * Header title to be rendered on the top of the column in log view. + */ +caption: string, +/** + * Description to be shown as tooltip for the column. + */ +description: string, +/** + * Width of column (-1) for unlimited. + */ +width: number, }; /** * Represents the options needs to render columns information if they exist. */ -export type ColumnsRenderOptions = { - /** - * List of columns infos providing the needed information for each column in log view. - * - * Note: The count of this list must match the count of the column of each log message. - */ - columns: Array; - /** - * Minimum column width. - */ - min_width: number; - /** - * Maximum column width. - */ - max_width: number; -}; +export type ColumnsRenderOptions = { +/** + * List of columns infos providing the needed information for each column in log view. + * + * Note: The count of this list must match the count of the column of each log message. + */ +columns: Array, +/** + * Minimum column width. + */ +min_width: number, +/** + * Maximum column width. + */ +max_width: number, }; /** * Represents the informations of an invalid plugin. */ -export type InvalidPluginEntity = { - /** - * Directory path of the plugin. Qualify as ID for the plugin. - */ - dir_path: string; - /** - * Represents the plugin type. - */ - plugin_type: PluginType; -}; +export type InvalidPluginEntity = { +/** + * Directory path of the plugin. Qualify as ID for the plugin. + */ +dir_path: string, +/** + * Represents the plugin type. + */ +plugin_type: PluginType, }; /** * Represents a list of [`InvalidPluginEntity`]. @@ -60,165 +57,128 @@ export type InvalidPluginsList = Array; /** * Provides additional information to be rendered in the log view. */ -export type ParserRenderOptions = { - /** - * Rendering information for the column if log messages have multiple columns. - * - * # Note: - * The count of the provided columns must match the count of the columns of each log message as well. - */ - columns_options: ColumnsRenderOptions | null; -}; +export type ParserRenderOptions = { +/** + * Rendering information for the column if log messages have multiple columns. + * + * # Note: + * The count of the provided columns must match the count of the columns of each log message as well. + */ +columns_options: ColumnsRenderOptions | null, }; /** * General settings for all byte-sources as plugins */ -export type PluginByteSourceGeneralSettings = { placeholder: string }; +export type PluginByteSourceGeneralSettings = { placeholder: string, }; /** * Settings for the Plugin Byte-Sources. */ -export type PluginByteSourceSettings = { - plugin_path: string; - general_settings: PluginByteSourceGeneralSettings; - plugin_configs: Array; -}; +export type PluginByteSourceSettings = { plugin_path: string, general_settings: PluginByteSourceGeneralSettings, plugin_configs: Array, }; /** * Represents a configuration item, which includes an identifier and its corresponding value. */ -export type PluginConfigItem = { id: string; value: PluginConfigValue }; +export type PluginConfigItem = { id: string, value: PluginConfigValue, }; /** * Represents the schema for a configuration item. */ -export type PluginConfigSchemaItem = { - id: string; - title: string; - description: string | null; - input_type: PluginConfigSchemaType; -}; +export type PluginConfigSchemaItem = { id: string, title: string, description: string | null, input_type: PluginConfigSchemaType, }; /** * Defines the possible input types for configuration schemas. */ -export type PluginConfigSchemaType = - | { Boolean: boolean } - | { Integer: number } - | { Float: number } - | { Text: string } - | 'Directories' - | { Files: Array } - | { Dropdown: [Array, string] }; +export type PluginConfigSchemaType = { "Boolean": boolean } | { "Integer": number } | { "Float": number } | { "Text": string } | "Directories" | { "Files": Array } | { "Dropdown": [Array, string] }; /** * Represents the value of a configuration item. */ -export type PluginConfigValue = - | { Boolean: boolean } - | { Integer: number } - | { Float: number } - | { Text: string } - | { Directories: Array } - | { Files: Array } - | { Dropdown: string }; +export type PluginConfigValue = { "Boolean": boolean } | { "Integer": number } | { "Float": number } | { "Text": string } | { "Directories": Array } | { "Files": Array } | { "Dropdown": string }; /** * Represents an installed plugin entity informations and configurations. */ -export type PluginEntity = { - /** - * Directory path of the plugin. Qualify as ID for the plugin. - */ - dir_path: string; - /** - * Represents the plugin type. - */ - plugin_type: PluginType; - /** - * Include various information about the plugin. - */ - info: PluginInfo; - /** - * Provides Plugins Metadata from separate source than the plugin binary. - * Currently they are saved inside plugin `*.toml` file. - */ - metadata: PluginMetadata; - /** - * Path of the readme file for the plugin to be rendered on the front-end - */ - readme_path: string | null; -}; +export type PluginEntity = { +/** + * Directory path of the plugin. Qualify as ID for the plugin. + */ +dir_path: string, +/** + * Represents the plugin type. + */ +plugin_type: PluginType, +/** + * Include various information about the plugin. + */ +info: PluginInfo, +/** + * Provides Plugins Metadata from separate source than the plugin binary. + * Currently they are saved inside plugin `*.toml` file. + */ +metadata: PluginMetadata, +/** + * Path of the readme file for the plugin to be rendered on the front-end + */ +readme_path: string | null, }; /** * Contains the infos and options for a valid plugin. */ -export type PluginInfo = { - wasm_file_path: string; - api_version: SemanticVersion; - plugin_version: SemanticVersion; - config_schemas: Array; - render_options: RenderOptions; -}; +export type PluginInfo = { wasm_file_path: string, api_version: SemanticVersion, plugin_version: SemanticVersion, config_schemas: Array, render_options: RenderOptions, }; /** * Represents different levels of logging severity for a plugin. */ -export type PluginLogLevel = 'Err' | 'Warn' | 'Debug' | 'Info'; +export type PluginLogLevel = "Err" | "Warn" | "Debug" | "Info"; /** * Represents a log message generated by a plugin. */ -export type PluginLogMessage = { - /** - * The severity level of the log message. - */ - level: PluginLogLevel; - /** - * The timestamp of when the log message was generated, represented as a Unix timestamp. - */ - timestamp: bigint; - /** - * The actual content of the log message. - */ - msg: string; -}; +export type PluginLogMessage = { +/** + * The severity level of the log message. + */ +level: PluginLogLevel, +/** + * The timestamp of when the log message was generated, represented as a Unix timestamp. + */ +timestamp: bigint, +/** + * The actual content of the log message. + */ +msg: string, }; /** * Represents the plugins metadata like title, description... */ -export type PluginMetadata = { title: string; description: string | null }; +export type PluginMetadata = { title: string, description: string | null, }; /** * General settings for all parsers as plugins */ -export type PluginParserGeneralSettings = { placeholder: string }; +export type PluginParserGeneralSettings = { placeholder: string, }; /** * Settings for the Plugins parser. */ -export type PluginParserSettings = { - plugin_path: string; - general_settings: PluginParserGeneralSettings; - plugin_configs: Array; -}; +export type PluginParserSettings = { plugin_path: string, general_settings: PluginParserGeneralSettings, plugin_configs: Array, }; /** * Maintains the state of a plugin, including its log messages. * Represented as a struct (but not just a vector) to reserve * a space for a future fields. */ -export type PluginRunData = { - /** - * A collection of log messages associated with the plugin. - */ - logs: Array; -}; +export type PluginRunData = { +/** + * A collection of log messages associated with the plugin. + */ +logs: Array, }; /** * Represents plugins main types */ -export type PluginType = 'Parser' | 'ByteSource'; +export type PluginType = "Parser" | "ByteSource"; /** * Represents a list of [`PluginEntity`]. @@ -233,9 +193,9 @@ export type PluginsPathsList = Array; /** * Represents the render options (columns headers, etc.) for the plugins. */ -export type RenderOptions = { Parser: ParserRenderOptions } | 'ByteSource'; +export type RenderOptions = { "Parser": ParserRenderOptions } | "ByteSource"; /** * Represents the semantic version used in the plugins system. */ -export type SemanticVersion = { major: number; minor: number; patch: number }; +export type SemanticVersion = { major: number, minor: number, patch: number, }; diff --git a/application/apps/indexer/stypes/src/callback/formating.rs b/application/apps/indexer/stypes/src/callback/formating.rs index 617973972..77c8195a9 100644 --- a/application/apps/indexer/stypes/src/callback/formating.rs +++ b/application/apps/indexer/stypes/src/callback/formating.rs @@ -37,6 +37,13 @@ impl std::fmt::Display for CallbackEvent { progress: _, } => write!(f, "Progress"), Self::SessionError(err) => write!(f, "SessionError: {err:?}"), + Self::SessionDescriptor { uuid, desc } => { + write!( + f, + "SessionDescriptor for {uuid}: {:?}/{:?}", + desc.s_desc, desc.p_desc + ) + } Self::OperationError { uuid, error } => { write!(f, "OperationError: {uuid}: {error:?}") } diff --git a/application/apps/indexer/stypes/src/callback/mod.rs b/application/apps/indexer/stypes/src/callback/mod.rs index 849408ab0..c82e361d2 100644 --- a/application/apps/indexer/stypes/src/callback/mod.rs +++ b/application/apps/indexer/stypes/src/callback/mod.rs @@ -70,6 +70,13 @@ pub enum CallbackEvent { /// - `Option`: The list of matches with log entry indices. SearchMapUpdated(Option), + SessionDescriptor { + /// Uuid of the observe operation, which adds new `SessionDescriptor` + uuid: Uuid, + /// Added `SessionDescriptor` + desc: SessionDescriptor, + }, + /// Triggered when the "value map" is updated. The "value map" is used to build charts /// from search results. Always triggered immediately after `SearchUpdated`. /// - `Option>`: The value map. diff --git a/application/apps/indexer/stypes/src/error/converting.rs b/application/apps/indexer/stypes/src/error/converting.rs index de1c2cd0b..0e948899a 100644 --- a/application/apps/indexer/stypes/src/error/converting.rs +++ b/application/apps/indexer/stypes/src/error/converting.rs @@ -1,5 +1,40 @@ use crate::*; +impl std::fmt::Display for NativeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + self.message + .as_ref() + .map(|s| s.to_owned()) + .unwrap_or_else(|| format!("Error without message ({})", self.kind)) + ) + } +} + +impl std::fmt::Display for NativeErrorKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::FileNotFound => "FileNotFound", + Self::UnsupportedFileType => "UnsupportedFileType", + Self::ComputationFailed => "ComputationFailed", + Self::Configuration => "Configuration", + Self::Interrupted => "Interrupted", + Self::OperationSearch => "OperationSearch", + Self::NotYetImplemented => "NotYetImplemented", + Self::ChannelError => "ChannelError", + Self::Io => "Io", + Self::Grabber => "Grabber", + Self::Plugins => "Plugins", + } + ) + } +} + impl From for NativeError { /// Converts a `std::io::Error` into a `NativeError`. /// diff --git a/application/apps/indexer/stypes/src/error/extending.rs b/application/apps/indexer/stypes/src/error/extending.rs index 6cbc536f9..0b5813d35 100644 --- a/application/apps/indexer/stypes/src/error/extending.rs +++ b/application/apps/indexer/stypes/src/error/extending.rs @@ -18,6 +18,13 @@ impl NativeError { message: Some(String::from(msg)), } } + pub fn io(msg: &str) -> Self { + NativeError { + severity: Severity::ERROR, + kind: NativeErrorKind::Io, + message: Some(String::from(msg)), + } + } } impl Severity { diff --git a/application/apps/indexer/stypes/src/lib.rs b/application/apps/indexer/stypes/src/lib.rs index eec6a7fd0..94fa905f8 100644 --- a/application/apps/indexer/stypes/src/lib.rs +++ b/application/apps/indexer/stypes/src/lib.rs @@ -100,6 +100,7 @@ mod lf_transition; mod miscellaneous; mod observe; mod operations; +mod options; mod plugins; mod progress; @@ -111,6 +112,7 @@ pub use lf_transition::*; pub use miscellaneous::*; pub use observe::*; pub use operations::*; +pub use options::*; pub use plugins::*; pub use progress::*; diff --git a/application/apps/indexer/stypes/src/miscellaneous/mod.rs b/application/apps/indexer/stypes/src/miscellaneous/mod.rs index df677ac90..579b7e908 100644 --- a/application/apps/indexer/stypes/src/miscellaneous/mod.rs +++ b/application/apps/indexer/stypes/src/miscellaneous/mod.rs @@ -14,7 +14,7 @@ use crate::*; #[cfg_attr( all(test, feature = "test_and_gen"), derive(TS), - ts(export, export_to = "miscellaneous.ts") + ts(export, export_to = "miscellaneous.ts", type = "Map") )] pub struct MapKeyValue(pub HashMap); @@ -55,8 +55,10 @@ pub struct Ranges(pub Vec); pub struct SourceDefinition { /// The unique identifier of the source. pub id: u16, + /// Parent observe opeartion Uuid + pub uuid: Uuid, /// The user-friendly name of the source for display purposes. - pub alias: String, + pub descriptor: SessionDescriptor, } /// A list of data sources. diff --git a/application/apps/indexer/stypes/src/miscellaneous/proptest.rs b/application/apps/indexer/stypes/src/miscellaneous/proptest.rs index e9b901fc1..4d2c54868 100644 --- a/application/apps/indexer/stypes/src/miscellaneous/proptest.rs +++ b/application/apps/indexer/stypes/src/miscellaneous/proptest.rs @@ -39,13 +39,17 @@ impl Arbitrary for SourceDefinition { /// Implements the `Arbitrary` trait for `SourceDefinition` to generate random instances. /// /// # Details - /// - Generates random `id` (`u16`) and `alias` (`String`) values. + /// - Generates random `id` (`u16`) and `alias` (`SessionDescriptor`) values. type Parameters = (); type Strategy = BoxedStrategy; fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { - (any::(), any::()) - .prop_map(|(id, alias)| SourceDefinition { id, alias }) + (any::(), any::()) + .prop_map(|(id, descriptor)| SourceDefinition { + id, + uuid: Uuid::new_v4(), + descriptor, + }) .boxed() } } diff --git a/application/apps/indexer/stypes/src/observe/converting.rs b/application/apps/indexer/stypes/src/observe/converting.rs new file mode 100644 index 000000000..e2e8a38b2 --- /dev/null +++ b/application/apps/indexer/stypes/src/observe/converting.rs @@ -0,0 +1,12 @@ +use crate::*; + +/// Converts a `Vec` into an `AttachmentList`. +/// +/// This implementation allows you to create an `IdentList` directly from a vector of +/// `Ident` objects. It simplifies the conversion and ensures compatibility +/// between these types. +impl From> for IdentList { + fn from(value: Vec) -> Self { + Self(value) + } +} diff --git a/application/apps/indexer/stypes/src/observe/extending.rs b/application/apps/indexer/stypes/src/observe/extending.rs index 5e9415b68..8ab2607c3 100644 --- a/application/apps/indexer/stypes/src/observe/extending.rs +++ b/application/apps/indexer/stypes/src/observe/extending.rs @@ -3,6 +3,36 @@ use std::net::IpAddr; use crate::*; use thiserror::Error; +impl ComponentsList { + pub fn new(sources: Vec, parsers: Vec) -> Self { + Self { parsers, sources } + } +} +impl SessionSetup { + pub fn inherit(&self, origin: SessionAction) -> Self { + Self { + origin, + parser: self.parser.clone(), + source: self.source.clone(), + } + } +} +impl SessionDescriptor { + pub fn new(source: Ident, parser: Ident) -> Self { + Self { + parser, + source, + p_desc: None, + s_desc: None, + } + } + pub fn set_parser_desc(&mut self, desc: Option) { + self.p_desc = desc; + } + pub fn set_source_desc(&mut self, desc: Option) { + self.s_desc = desc; + } +} impl ObserveOptions { /// Creates a new `ObserveOptions` instance for a file. /// diff --git a/application/apps/indexer/stypes/src/observe/mod.rs b/application/apps/indexer/stypes/src/observe/mod.rs index 3e1d7b93a..529df7bed 100644 --- a/application/apps/indexer/stypes/src/observe/mod.rs +++ b/application/apps/indexer/stypes/src/observe/mod.rs @@ -1,16 +1,149 @@ #[cfg(feature = "rustcore")] +mod converting; +#[cfg(feature = "rustcore")] mod extending; #[cfg(feature = "nodejs")] mod nodejs; #[cfg(test)] mod proptest; +use std::ops::RangeInclusive; + #[cfg(feature = "rustcore")] pub use extending::*; use crate::*; use dlt_core::filtering::DltFilterConfig; +#[derive(Clone, Serialize, Deserialize, Debug)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "observe.ts") +)] +/// Described user basic action. Source means, user is selecting custom source & parser +pub enum SessionAction { + File(PathBuf), + Files(Vec), + ExportRaw(Vec, Vec>, PathBuf), + Source, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "observe.ts") +)] +pub enum ComponentDef { + Source(ComponentOptions), + Parser(ComponentOptions), +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "observe.ts") +)] +pub struct ComponentsList { + pub parsers: Vec, + pub sources: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "observe.ts") +)] +pub struct SessionSetup { + pub origin: SessionAction, + pub parser: ComponentOptions, + pub source: ComponentOptions, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "observe.ts") +)] +pub struct SessionDescriptor { + /// Identifier of the parser being used. Provides a general description of the parser. + pub parser: Ident, + /// Identifier of the source being used. Provides a general description of the source. + pub source: Ident, + /// Parser description in the context of the current session. + pub p_desc: Option, + /// Source description in the context of the current session. For example, instead of "UDP", it might be "UDP on 0.0.0.0::8888". + pub s_desc: Option, +} +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "observe.ts") +)] +pub struct IdentList(pub Vec); + +#[derive(Clone, Serialize, Deserialize, Debug)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "observe.ts") +)] +pub struct Ident { + /// A short, human-readable name of the component (parser/source), independent of context. + /// Used on the client side to display available components. + pub name: String, + + /// A more detailed description of the component. This information is displayed in a separate + /// area and can contain an extended explanation. + pub desc: String, + + /// The type of data the component produces or expects. + /// For sources, this is the type of data they emit. + /// For parsers, this is the type of data they expect as input. + pub io: IODataType, + + /// A unique identifier of the component. + pub uuid: Uuid, +} + +/// Represents the type of a component within the system. +/// +/// The component type indicates the general domain of responsibility and +/// functional role of the component. It is used to categorize components +/// according to their purpose in the data processing pipeline. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Ord, Eq, Hash)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "observe.ts") +)] +pub enum ComponentType { + /// A standard parser used during session processing. + /// + /// These parsers transform raw input data into a structured representation + /// that can be stored in session files and later viewed or analyzed by the user. + Parser, + + /// A data source component. + /// + /// Responsible for providing input data to the system, such as reading from + /// a file, a network stream, or another external interface. + Source, +} + /// Multicast configuration information. /// - `multiaddr`: A valid multicast address. /// - `interface`: The address of the local interface used to join the multicast group. diff --git a/application/apps/indexer/stypes/src/observe/nodejs.rs b/application/apps/indexer/stypes/src/observe/nodejs.rs index b79f2bd9d..5d22c1d81 100644 --- a/application/apps/indexer/stypes/src/observe/nodejs.rs +++ b/application/apps/indexer/stypes/src/observe/nodejs.rs @@ -12,3 +12,7 @@ try_into_js!(UDPTransportConfig); try_into_js!(FileFormat); try_into_js!(ObserveOrigin); try_into_js!(ObserveOptions); +try_into_js!(Ident); +try_into_js!(IdentList); +try_into_js!(SessionAction); +try_into_js!(ComponentsList); diff --git a/application/apps/indexer/stypes/src/observe/proptest.rs b/application/apps/indexer/stypes/src/observe/proptest.rs index a899f9f30..d6e0d10e9 100644 --- a/application/apps/indexer/stypes/src/observe/proptest.rs +++ b/application/apps/indexer/stypes/src/observe/proptest.rs @@ -1,6 +1,43 @@ use crate::*; use dlt_core::filtering::DltFilterConfig; +impl Arbitrary for SessionDescriptor { + /// Implements the `Arbitrary` trait for `SessionDescriptor` to generate random instances. + /// + /// # Details + /// - Generates random `id` (`u16`) and `alias` (`String`) values. + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { + (any::(), any::(), any::()) + .prop_map(|(parser, source, desc)| SessionDescriptor { + parser, + source, + p_desc: Some(desc.clone()), + s_desc: Some(desc), + }) + .boxed() + } +} + +impl Arbitrary for Ident { + /// Implements the `Arbitrary` trait for `Ident` to generate random instances. + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { + (any::(), any::(), any::()) + .prop_map(|(name, desc, io)| Ident { + name, + desc, + io, + uuid: Uuid::new_v4(), + }) + .boxed() + } +} + impl Arbitrary for MulticastInfo { type Parameters = (); type Strategy = BoxedStrategy; diff --git a/application/apps/indexer/stypes/src/options/extending.rs b/application/apps/indexer/stypes/src/options/extending.rs new file mode 100644 index 000000000..800a2531f --- /dev/null +++ b/application/apps/indexer/stypes/src/options/extending.rs @@ -0,0 +1,364 @@ +/// A set of utility traits designed to simplify working with component configuration fields. +/// +/// While the use of these traits is entirely optional, they can significantly reduce boilerplate code +/// and improve overall readability when extracting typed values from field definitions. +use crate::*; + +/// Helper function that creates a `NativeError` indicating a missing field in configuration. +pub fn missed_field_err(field: &str) -> NativeError { + NativeError { + kind: NativeErrorKind::Configuration, + severity: Severity::ERROR, + message: Some(format!("Missed field {field} ")), + } +} + +/// A wrapper structure that holds an extracted field value along with its ID. +pub struct Extracted<'a, T> { + /// The extracted value. + pub value: T, + /// The identifier of the field from which the value was extracted. + pub id: &'a str, +} + +impl<'a, T> Extracted<'a, T> { + /// Creates a new `Extracted` instance with the provided field ID and value. + pub fn new(id: &'a str, value: T) -> Self { + Self { value, id } + } +} + +/// Internal helper function used to avoid code duplication. +/// Searches for a field by its key and attempts to extract a value of the specified type. +fn extract_from_fields_by_key<'a, T>(fields: &'a [Field], key: &str) -> Option> +where + &'a Field: ExtractAs, +{ + fields + .iter() + .find(|field| field.id == key) + .and_then(|field| { + field + .extract_as() + .map(|v| Extracted::new(field.id.as_str(), v)) + }) +} + +/// Trait that provides typed extraction from a collection of `Field` objects using a string key. +pub trait ExtractByKey { + /// Attempts to extract a value of type `T` by field ID. + /// + /// Returns `Some(Extracted)` if the field exists and the value can be extracted, + /// otherwise returns `None`. + fn extract_by_key<'a>(&'a self, key: &str) -> Option>; +} + +impl ExtractByKey for &[Field] { + fn extract_by_key<'a>(&'a self, key: &str) -> Option> { + extract_from_fields_by_key(self, key) + } +} + +impl ExtractByKey for &[Field] { + fn extract_by_key<'a>(&'a self, key: &str) -> Option> { + extract_from_fields_by_key(self, key) + } +} + +impl ExtractByKey for &[Field] { + fn extract_by_key<'a>(&'a self, key: &str) -> Option> { + extract_from_fields_by_key(self, key) + } +} + +impl ExtractByKey for &[Field] { + fn extract_by_key<'a>(&'a self, key: &str) -> Option> { + extract_from_fields_by_key(self, key) + } +} + +impl ExtractByKey for &[Field] { + fn extract_by_key<'a>(&'a self, key: &str) -> Option> { + extract_from_fields_by_key(self, key) + } +} + +impl ExtractByKey for &[Field] { + fn extract_by_key<'a>(&'a self, key: &str) -> Option> { + extract_from_fields_by_key(self, key) + } +} + +impl ExtractByKey for &[Field] { + fn extract_by_key<'a>(&'a self, key: &str) -> Option> { + extract_from_fields_by_key(self, key) + } +} + +impl ExtractByKey for &[Field] { + fn extract_by_key<'a>(&'a self, key: &str) -> Option> { + extract_from_fields_by_key(self, key) + } +} + +impl<'l> ExtractByKey<&'l Vec> for &'l [Field] { + fn extract_by_key<'a>(&'a self, key: &str) -> Option>> { + extract_from_fields_by_key(self, key) + } +} + +impl<'l> ExtractByKey<&'l Vec> for &'l [Field] { + fn extract_by_key<'a>(&'a self, key: &str) -> Option>> { + extract_from_fields_by_key(self, key) + } +} + +impl<'l> ExtractByKey<&'l HashMap>> for &'l [Field] { + fn extract_by_key<'a>( + &'a self, + key: &str, + ) -> Option>>> { + extract_from_fields_by_key(self, key) + } +} +/// A trait that provides type-specific extraction logic from a `Field`. +/// +/// This trait allows extracting a value of type `T` from a `Field` +/// if the internal representation supports it. +/// +/// Returns `Some(T)` if the conversion is successful, otherwise `None`. +pub trait ExtractAs { + /// Attempts to extract a value of type `T` from the field. + fn extract_as(&self) -> Option; +} + +impl ExtractAs for &Field { + fn extract_as(&self) -> Option { + match &self.value { + Value::String(value) => Some(value.to_owned()), + Value::Boolean(..) + | Value::Directories(..) + | Value::Directory(..) + | Value::Fields(..) + | Value::File(..) + | Value::Files(..) + | Value::KeyNumber(..) + | Value::KeyNumbers(..) + | Value::KeyString(..) + | Value::KeyStrings(..) + | Value::Number(..) + | Value::Numbers(..) + | Value::Strings(..) => None, + } + } +} + +impl ExtractAs for &Field { + fn extract_as(&self) -> Option { + match &self.value { + Value::Number(value) => Some(*value), + Value::Boolean(..) + | Value::Directories(..) + | Value::Directory(..) + | Value::Fields(..) + | Value::File(..) + | Value::Files(..) + | Value::KeyNumber(..) + | Value::KeyNumbers(..) + | Value::KeyString(..) + | Value::KeyStrings(..) + | Value::String(..) + | Value::Numbers(..) + | Value::Strings(..) => None, + } + } +} + +impl ExtractAs for &Field { + fn extract_as(&self) -> Option { + match &self.value { + Value::Number(value) => (*value).try_into().ok(), + Value::Boolean(..) + | Value::Directories(..) + | Value::Directory(..) + | Value::Fields(..) + | Value::File(..) + | Value::Files(..) + | Value::KeyNumber(..) + | Value::KeyNumbers(..) + | Value::KeyString(..) + | Value::KeyStrings(..) + | Value::String(..) + | Value::Numbers(..) + | Value::Strings(..) => None, + } + } +} + +impl ExtractAs for &Field { + fn extract_as(&self) -> Option { + match &self.value { + Value::Number(value) => (*value).try_into().ok(), + Value::Boolean(..) + | Value::Directories(..) + | Value::Directory(..) + | Value::Fields(..) + | Value::File(..) + | Value::Files(..) + | Value::KeyNumber(..) + | Value::KeyNumbers(..) + | Value::KeyString(..) + | Value::KeyStrings(..) + | Value::String(..) + | Value::Numbers(..) + | Value::Strings(..) => None, + } + } +} + +impl ExtractAs for &Field { + fn extract_as(&self) -> Option { + match &self.value { + Value::Number(value) => (*value).try_into().ok(), + Value::Boolean(..) + | Value::Directories(..) + | Value::Directory(..) + | Value::Fields(..) + | Value::File(..) + | Value::Files(..) + | Value::KeyNumber(..) + | Value::KeyNumbers(..) + | Value::KeyString(..) + | Value::KeyStrings(..) + | Value::String(..) + | Value::Numbers(..) + | Value::Strings(..) => None, + } + } +} + +impl ExtractAs for &Field { + fn extract_as(&self) -> Option { + match &self.value { + Value::Number(value) => (*value).try_into().ok(), + Value::Boolean(..) + | Value::Directories(..) + | Value::Directory(..) + | Value::Fields(..) + | Value::File(..) + | Value::Files(..) + | Value::KeyNumber(..) + | Value::KeyNumbers(..) + | Value::KeyString(..) + | Value::KeyStrings(..) + | Value::String(..) + | Value::Numbers(..) + | Value::Strings(..) => None, + } + } +} + +impl ExtractAs for &Field { + fn extract_as(&self) -> Option { + match &self.value { + Value::Number(value) => (*value).try_into().ok(), + Value::Boolean(..) + | Value::Directories(..) + | Value::Directory(..) + | Value::Fields(..) + | Value::File(..) + | Value::Files(..) + | Value::KeyNumber(..) + | Value::KeyNumbers(..) + | Value::KeyString(..) + | Value::KeyStrings(..) + | Value::String(..) + | Value::Numbers(..) + | Value::Strings(..) => None, + } + } +} + +impl ExtractAs for &Field { + fn extract_as(&self) -> Option { + match &self.value { + Value::Boolean(value) => Some(*value), + Value::Number(..) + | Value::Directories(..) + | Value::Directory(..) + | Value::Fields(..) + | Value::File(..) + | Value::Files(..) + | Value::KeyNumber(..) + | Value::KeyNumbers(..) + | Value::KeyString(..) + | Value::KeyStrings(..) + | Value::String(..) + | Value::Numbers(..) + | Value::Strings(..) => None, + } + } +} + +impl<'l> ExtractAs<&'l Vec> for &'l Field { + fn extract_as(&self) -> Option<&'l Vec> { + match &self.value { + Value::Fields(value) => Some(value), + Value::Number(..) + | Value::Directories(..) + | Value::Directory(..) + | Value::Boolean(..) + | Value::File(..) + | Value::Files(..) + | Value::KeyNumber(..) + | Value::KeyNumbers(..) + | Value::KeyString(..) + | Value::KeyStrings(..) + | Value::String(..) + | Value::Numbers(..) + | Value::Strings(..) => None, + } + } +} + +impl<'l> ExtractAs<&'l Vec> for &'l Field { + fn extract_as(&self) -> Option<&'l Vec> { + match &self.value { + Value::Files(value) => Some(value), + Value::Number(..) + | Value::Directories(..) + | Value::Directory(..) + | Value::Boolean(..) + | Value::File(..) + | Value::Fields(..) + | Value::KeyNumber(..) + | Value::KeyNumbers(..) + | Value::KeyString(..) + | Value::KeyStrings(..) + | Value::String(..) + | Value::Numbers(..) + | Value::Strings(..) => None, + } + } +} + +impl<'l> ExtractAs<&'l HashMap>> for &'l Field { + fn extract_as(&self) -> Option<&'l HashMap>> { + match &self.value { + Value::KeyStrings(value) => Some(value), + Value::Number(..) + | Value::Directories(..) + | Value::Directory(..) + | Value::Boolean(..) + | Value::File(..) + | Value::Fields(..) + | Value::KeyNumber(..) + | Value::KeyNumbers(..) + | Value::KeyString(..) + | Value::Files(..) + | Value::String(..) + | Value::Numbers(..) + | Value::Strings(..) => None, + } + } +} diff --git a/application/apps/indexer/stypes/src/options/mod.rs b/application/apps/indexer/stypes/src/options/mod.rs new file mode 100644 index 000000000..b3e8e8b0f --- /dev/null +++ b/application/apps/indexer/stypes/src/options/mod.rs @@ -0,0 +1,300 @@ +#[cfg(feature = "rustcore")] +mod extending; +#[cfg(feature = "nodejs")] +mod nodejs; +#[cfg(test)] +mod proptest; + +#[cfg(feature = "rustcore")] +pub use extending::*; + +use crate::*; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "options.ts") +)] +pub enum OutputRender { + Columns(Vec<(String, usize)>), + PlaitText, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "options.ts") +)] +pub enum IODataType { + PlaitText, + Raw, + NetworkFramePayload, + Multiple(Vec), + Any, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "options.ts") +)] +pub struct ComponentsOptionsList { + #[cfg_attr( + all(test, feature = "test_and_gen"), + ts(type = "Map") + )] + pub options: HashMap>, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "options.ts") +)] +pub struct FieldsValidationErrors { + #[cfg_attr(all(test, feature = "test_and_gen"), ts(type = "Map"))] + pub errors: HashMap, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "options.ts") +)] +pub struct ComponentOptions { + pub fields: Vec, + pub uuid: Uuid, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "options.ts") +)] +pub enum FieldDesc { + Static(StaticFieldDesc), + Lazy(LazyFieldDesc), +} +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "options.ts") +)] +pub struct StaticFieldDesc { + pub id: String, + pub name: String, + pub desc: String, + pub required: bool, + pub interface: ValueInput, + pub binding: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "options.ts") +)] +pub struct LazyFieldDesc { + pub id: String, + pub name: String, + pub desc: String, + pub binding: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "options.ts") +)] +pub struct Field { + pub id: String, + pub value: Value, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "options.ts") +)] +pub struct FieldList(pub Vec); + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "options.ts") +)] +pub enum ValueInput { + /// * `bool` - default value + Checkbox(bool), + /// * `i64` - default value + #[cfg_attr(all(test, feature = "test_and_gen"), ts(type = "number"))] + Number(i64), + /// * `String` - default value + /// * `String` - placeholder + String(String, String), + #[cfg_attr( + all(test, feature = "test_and_gen"), + ts(type = "[Array, number]") + )] + Numbers(Vec, i64), + Strings(Vec, String), + NamedBools(Vec<(String, bool)>), + #[cfg_attr( + all(test, feature = "test_and_gen"), + ts(type = "[Array<[string, number]>, number]") + )] + NamedNumbers(Vec<(String, i64)>, i64), + NamedStrings(Vec<(String, String)>, String), + #[cfg_attr(all(test, feature = "test_and_gen"), ts(type = "Map"))] + KeyNumber(HashMap), + #[cfg_attr( + all(test, feature = "test_and_gen"), + ts(type = "Map") + )] + KeyNumbers(HashMap>), + #[cfg_attr(all(test, feature = "test_and_gen"), ts(type = "Map"))] + KeyString(HashMap), + #[cfg_attr( + all(test, feature = "test_and_gen"), + ts(type = "Map") + )] + KeyStrings(HashMap>), + #[cfg_attr( + all(test, feature = "test_and_gen"), + ts(type = "[Map>>, Map]") + )] + /// `HashMap>>` - table of data + /// `HashMap` - dictionary of headers + NestedNumbersMap( + HashMap>>, + HashMap, + ), + #[cfg_attr( + all(test, feature = "test_and_gen"), + ts(type = "[Map>>, Map]") + )] + /// `HashMap>>` - table of data + /// `HashMap` - dictionary of headers + NestedStringsMap( + HashMap>>, + HashMap, + ), + Directories, + Files(Vec), + File(Vec), + Directory(Option), + Timezone, + InputsCollection { + elements: Vec, + add_title: String, + }, + FieldsCollection { + elements: Vec, + add_title: String, + }, + Bound { + output: Box, + inputs: Vec, + }, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "options.ts") +)] +pub enum Value { + Boolean(bool), + #[cfg_attr(all(test, feature = "test_and_gen"), ts(type = "number"))] + Number(i64), + #[cfg_attr(all(test, feature = "test_and_gen"), ts(type = "Array"))] + Numbers(Vec), + String(String), + Strings(Vec), + Directories(Vec), + Files(Vec), + File(PathBuf), + Directory(PathBuf), + #[cfg_attr(all(test, feature = "test_and_gen"), ts(type = "Map"))] + KeyNumber(HashMap), + #[cfg_attr( + all(test, feature = "test_and_gen"), + ts(type = "Map") + )] + KeyNumbers(HashMap>), + #[cfg_attr(all(test, feature = "test_and_gen"), ts(type = "Map"))] + KeyString(HashMap), + #[cfg_attr( + all(test, feature = "test_and_gen"), + ts(type = "Map") + )] + KeyStrings(HashMap>), + Fields(Vec), +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "options.ts") +)] +pub struct FieldLoadingError { + pub id: String, + pub err: String, +} + +/// Represents events sent to the client. +#[derive(Debug, Serialize, Deserialize, Clone)] +#[extend::encode_decode] +#[cfg_attr( + all(test, feature = "test_and_gen"), + derive(TS), + ts(export, export_to = "options.ts") +)] +pub enum CallbackOptionsEvent { + /// Triggered when lazy field is ready + LoadingDone { + owner: Uuid, + fields: Vec, + }, + + /// Triggered when a lazy fields wasn't loaded. + LoadingErrors { + owner: Uuid, + errors: Vec, + }, + LoadingError { + owner: Uuid, + error: String, + fields: Vec, + }, + LoadingCancelled { + owner: Uuid, + fields: Vec, + }, + Destroyed, +} diff --git a/application/apps/indexer/stypes/src/options/nodejs.rs b/application/apps/indexer/stypes/src/options/nodejs.rs new file mode 100644 index 000000000..cea5dcb48 --- /dev/null +++ b/application/apps/indexer/stypes/src/options/nodejs.rs @@ -0,0 +1,12 @@ +use crate::*; + +try_into_js!(FieldDesc); +try_into_js!(StaticFieldDesc); +try_into_js!(LazyFieldDesc); +try_into_js!(Value); +try_into_js!(ValueInput); +try_into_js!(CallbackOptionsEvent); +try_into_js!(ComponentsOptionsList); +try_into_js!(FieldsValidationErrors); +try_into_js!(OutputRender); +try_into_js!(FieldList); diff --git a/application/apps/indexer/stypes/src/options/proptest.rs b/application/apps/indexer/stypes/src/options/proptest.rs new file mode 100644 index 000000000..d4a642454 --- /dev/null +++ b/application/apps/indexer/stypes/src/options/proptest.rs @@ -0,0 +1,26 @@ +use crate::*; + +impl Arbitrary for IODataType { + type Parameters = bool; + type Strategy = BoxedStrategy; + + fn arbitrary_with(nested: Self::Parameters) -> Self::Strategy { + if !nested { + prop_oneof![ + Just(IODataType::Raw), + Just(IODataType::PlaitText), + Just(IODataType::NetworkFramePayload), + IODataType::arbitrary_with(true) + .prop_map(|inner| IODataType::Multiple(vec![inner])) + ] + .boxed() + } else { + prop_oneof![ + Just(IODataType::Raw), + Just(IODataType::PlaitText), + Just(IODataType::NetworkFramePayload), + ] + .boxed() + } + } +} diff --git a/application/apps/indexer/stypes/src/plugins/nodejs.rs b/application/apps/indexer/stypes/src/plugins/nodejs.rs index 575b3c23b..a5802be10 100644 --- a/application/apps/indexer/stypes/src/plugins/nodejs.rs +++ b/application/apps/indexer/stypes/src/plugins/nodejs.rs @@ -19,3 +19,4 @@ try_into_js!(ParserRenderOptions); try_into_js!(PluginsList); try_into_js!(InvalidPluginsList); try_into_js!(PluginsPathsList); +try_into_js!(PluginRunData); diff --git a/application/apps/indexer/stypes/src/plugins/proptest.rs b/application/apps/indexer/stypes/src/plugins/proptest.rs index b974ad636..939d9adf6 100644 --- a/application/apps/indexer/stypes/src/plugins/proptest.rs +++ b/application/apps/indexer/stypes/src/plugins/proptest.rs @@ -245,7 +245,10 @@ impl Arbitrary for InvalidPluginEntity { fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { (any::(), any::()) - .prop_map(|(dir_path, plugin_type)| Self::new(dir_path, plugin_type)) + .prop_map(|(dir_path, plugin_type)| Self { + dir_path, + plugin_type, + }) .boxed() } } diff --git a/application/apps/list/Cargo.lock b/application/apps/list/Cargo.lock new file mode 100644 index 000000000..a3dc7fbf6 --- /dev/null +++ b/application/apps/list/Cargo.lock @@ -0,0 +1,129 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "list" +version = "0.1.0" +dependencies = [ + "uuid", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] diff --git a/application/apps/list/Cargo.toml b/application/apps/list/Cargo.toml new file mode 100644 index 000000000..17c093cd9 --- /dev/null +++ b/application/apps/list/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "list" +description = "List" +version = "0.1.0" +edition = "2021" + +[dependencies] +uuid = { version="1", features = ["serde", "v4"] } diff --git a/application/apps/list/src/main.rs b/application/apps/list/src/main.rs new file mode 100644 index 000000000..85cf6b735 --- /dev/null +++ b/application/apps/list/src/main.rs @@ -0,0 +1,10 @@ +const BIN_SOURCE_UUID: uuid::Uuid = uuid::Uuid::from_bytes([ + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, +]); +const DLT_PARSER_UUID: uuid::Uuid = uuid::Uuid::from_bytes([ + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +]); +fn main() { + println!("{BIN_SOURCE_UUID}"); + println!("{DLT_PARSER_UUID}"); +} diff --git a/application/apps/protocol/src/lib.rs b/application/apps/protocol/src/lib.rs index ab7b93c79..ef2cd0270 100644 --- a/application/apps/protocol/src/lib.rs +++ b/application/apps/protocol/src/lib.rs @@ -152,12 +152,6 @@ gen_encode_decode_fns!(ColumnInfo); gen_encode_decode_fns!(PluginsList); gen_encode_decode_fns!(InvalidPluginsList); gen_encode_decode_fns!(PluginsPathsList); -gen_encode_decode_fns!(CommandOutcome); -gen_encode_decode_fns!(CommandOutcome); -gen_encode_decode_fns!(CommandOutcome); -gen_encode_decode_fns!(CommandOutcome>); -gen_encode_decode_fns!(CommandOutcome>); -gen_encode_decode_fns!(CommandOutcome>); gen_encode_decode_fns!(CommandOutcome); gen_encode_decode_fns!(CommandOutcome); gen_encode_decode_fns!(CommandOutcome); @@ -205,3 +199,19 @@ gen_encode_decode_fns!(Point); gen_encode_decode_fns!(ResultSearchValues); gen_encode_decode_fns!(ResultScaledDistribution); gen_encode_decode_fns!(DltLevelDistribution); +gen_encode_decode_fns!(SessionAction); +gen_encode_decode_fns!(Ident); +gen_encode_decode_fns!(IdentList); +gen_encode_decode_fns!(ComponentsOptionsList); +gen_encode_decode_fns!(FieldDesc); +gen_encode_decode_fns!(LazyFieldDesc); +gen_encode_decode_fns!(StaticFieldDesc); +gen_encode_decode_fns!(CallbackOptionsEvent); +gen_encode_decode_fns!(ComponentType); +gen_encode_decode_fns!(FieldsValidationErrors); +gen_encode_decode_fns!(ComponentOptions); +gen_encode_decode_fns!(SessionSetup); +gen_encode_decode_fns!(SessionDescriptor); +gen_encode_decode_fns!(OutputRender); +gen_encode_decode_fns!(ComponentsList); +gen_encode_decode_fns!(FieldList); diff --git a/application/apps/rustcore/rs-bindings/Cargo.lock b/application/apps/rustcore/rs-bindings/Cargo.lock index 9c2fd9c2f..6b564494d 100644 --- a/application/apps/rustcore/rs-bindings/Cargo.lock +++ b/application/apps/rustcore/rs-bindings/Cargo.lock @@ -23,9 +23,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" @@ -258,7 +258,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -275,7 +275,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -302,7 +302,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -385,9 +385,9 @@ version = "0.1.1" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" dependencies = [ "allocator-api2", ] @@ -490,9 +490,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.25" +version = "1.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" +checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" dependencies = [ "jobserver", "libc", @@ -501,9 +501,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -823,7 +823,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -847,7 +847,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -858,7 +858,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -899,7 +899,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -909,7 +909,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.101", + "syn 2.0.102", +] + +[[package]] +name = "descriptor" +version = "0.1.0" +dependencies = [ + "log", + "stypes", + "thiserror 2.0.12", + "tokio", + "tokio-util", + "uuid", ] [[package]] @@ -968,7 +980,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.0", - "windows-sys 0.59.0", + "windows-sys 0.60.1", ] [[package]] @@ -990,7 +1002,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -1084,9 +1096,9 @@ dependencies = [ [[package]] name = "etherparse" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d8a704b617484e9d867a0423cd45f7577f008c4068e2e33378f8d3860a6d73" +checksum = "3ff83a5facf1a7cbfef93cfb48d6d4fb6a1f42d8ac2341a96b3255acb4d4f860" dependencies = [ "arrayvec", ] @@ -1124,7 +1136,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -1293,7 +1305,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -1366,7 +1378,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] @@ -1389,7 +1401,7 @@ checksum = "6a8661f18e73000bcea2f0d68262eb8ac1b1e951e10eb936d3f95b36201c136d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -1467,9 +1479,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "foldhash", "serde", @@ -1483,9 +1495,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "home" @@ -1682,9 +1694,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown", @@ -1997,9 +2009,9 @@ checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memfd" @@ -2062,9 +2074,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -2077,7 +2089,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -2303,7 +2315,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -2371,7 +2383,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2387,10 +2399,11 @@ dependencies = [ name = "parsers" version = "0.1.0" dependencies = [ - "byteorder", "chrono", "chrono-tz", + "descriptor", "dlt-core", + "file-tools", "lazy_static", "log", "memchr", @@ -2400,7 +2413,10 @@ dependencies = [ "someip-messages", "someip-payload", "someip-tools", + "stypes", "thiserror 2.0.12", + "tokio-util", + "uuid", ] [[package]] @@ -2493,18 +2509,22 @@ version = "0.1.0" dependencies = [ "anyhow", "blake3", + "descriptor", "dir_checksum", "dirs", "log", "parsers", "rand 0.9.1", + "register", "serde", "serde_json", "sources", "stypes", "thiserror 2.0.12", "tokio", + "tokio-util", "toml", + "uuid", "wasmtime", "wasmtime-wasi", ] @@ -2585,6 +2605,8 @@ dependencies = [ "stypes", "text_grep", "thiserror 2.0.12", + "tokio", + "tokio-stream", "tokio-util", "uuid", ] @@ -2810,11 +2832,28 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "register" +version = "0.1.0" +dependencies = [ + "descriptor", + "file-tools", + "indexmap", + "log", + "parsers", + "sources", + "stypes", + "thiserror 2.0.12", + "tokio", + "tokio-util", + "uuid", +] + [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -2962,7 +3001,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -2979,9 +3018,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -3024,6 +3063,7 @@ version = "0.1.0" dependencies = [ "blake3", "crossbeam-channel", + "descriptor", "dirs", "dlt-core", "envvars", @@ -3037,6 +3077,7 @@ dependencies = [ "parsers", "plugins_host", "processor", + "register", "rustc-hash", "serde", "serde_json", @@ -3112,9 +3153,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] @@ -3168,7 +3209,10 @@ dependencies = [ "async-stream", "bufread", "bytes", + "descriptor", + "envvars", "etherparse", + "file-tools", "futures", "indexer_base", "lazy_static", @@ -3177,6 +3221,7 @@ dependencies = [ "pcap-parser", "regex", "serde", + "serialport", "shellexpand", "socket2", "stypes", @@ -3226,6 +3271,7 @@ dependencies = [ "serde", "thiserror 2.0.12", "tokio", + "tokio-util", "uuid", "walkdir", ] @@ -3243,9 +3289,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462" dependencies = [ "proc-macro2", "quote", @@ -3260,7 +3306,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -3347,7 +3393,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -3358,7 +3404,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -3437,7 +3483,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -3484,9 +3530,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -3496,18 +3542,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", @@ -3519,9 +3565,9 @@ dependencies = [ [[package]] name = "toml_write" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tracing" @@ -3536,20 +3582,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -3592,7 +3638,7 @@ checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -3639,9 +3685,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unicode-xid" @@ -3746,9 +3792,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -3781,7 +3827,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", "wasm-bindgen-shared", ] @@ -3816,7 +3862,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3842,12 +3888,12 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.233.0" +version = "0.234.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9679ae3cf7cfa2ca3a327f7fab97f27f3294d402fd1a76ca8ab514e17973e4d3" +checksum = "170a0157eef517a179f2d20ed7c68df9c3f7f6c1c047782d488bf5a464174684" dependencies = [ "leb128fmt", - "wasmparser 0.233.0", + "wasmparser 0.234.0", ] [[package]] @@ -3865,9 +3911,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.233.0" +version = "0.234.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b51cb03afce7964bbfce46602d6cb358726f36430b6ba084ac6020d8ce5bc102" +checksum = "be22e5a8f600afce671dd53c8d2dd26b4b7aa810fd18ae27dfc49737f3e02fc5" dependencies = [ "bitflags 2.9.1", "indexmap", @@ -3980,7 +4026,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", "wasmtime-component-util", "wasmtime-wit-bindgen", "wit-parser", @@ -4107,7 +4153,7 @@ checksum = "55b39ffeda28be925babb2d45067d8ba2c67d2227328c5364d23b4152eba9950" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -4194,24 +4240,24 @@ dependencies = [ [[package]] name = "wast" -version = "233.0.0" +version = "234.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eaf4099d8d0c922b83bf3c90663f5666f0769db9e525184284ebbbdb1dd2180" +checksum = "f5fc6bea84cc3007ad3e68c6223f52095e6093eec4d7ebdff355f2c952fd9007" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width", - "wasm-encoder 0.233.0", + "wasm-encoder 0.234.0", ] [[package]] name = "wat" -version = "1.233.0" +version = "1.234.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d9bc80f5e4b25ea086ef41b91ccd244adde45d931c384d94a8ff64ab8bd7d87" +checksum = "1a4807d67cf6885965d2f118744fd0ad20ffb9082c875de532af37b499e65aa6" dependencies = [ - "wast 233.0.0", + "wast 234.0.0", ] [[package]] @@ -4249,7 +4295,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", "witx", ] @@ -4261,7 +4307,7 @@ checksum = "71ac603ee46847d2e3c142ba715d326f1045155c7758f4e8dd001d5f92810c12" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", "wiggle-generate", ] @@ -4336,7 +4382,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -4347,14 +4393,14 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "d3bfe459f85da17560875b8bf1423d6f113b7a87a5d942e7da0ac71be7c61f8b" [[package]] name = "windows-result" @@ -4380,7 +4426,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -4389,7 +4435,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b36e9ed89376c545e20cbf5a13c306b49106b21b9d1d4f9cb9a1cb6b1e9ee06a" +dependencies = [ + "windows-targets 0.53.1", ] [[package]] @@ -4398,14 +4453,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30357ec391cde730f8fbfcdc29adc47518b06504528df977ab5af02ef23fdee9" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -4414,53 +4485,101 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] @@ -4540,7 +4659,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", "synstructure", ] @@ -4561,7 +4680,7 @@ checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -4581,7 +4700,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", "synstructure", ] @@ -4615,7 +4734,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] diff --git a/application/apps/rustcore/rs-bindings/src/js/components/mod.rs b/application/apps/rustcore/rs-bindings/src/js/components/mod.rs new file mode 100644 index 000000000..29aaea5eb --- /dev/null +++ b/application/apps/rustcore/rs-bindings/src/js/components/mod.rs @@ -0,0 +1,571 @@ +use log::{debug, error}; +use node_bindgen::{core::buffer::JSArrayBuffer, derive::node_bindgen}; +use session::register::SessionRegister; +use std::{str::FromStr, thread}; +use tokio::{runtime::Runtime, sync::oneshot}; +use uuid::Uuid; + +struct Components { + session: Option, +} + +#[node_bindgen] +impl Components { + #[node_bindgen(constructor)] + pub fn new() -> Self { + Self { session: None } + } + + /// Initializes a new session bound to the `Components` instance at the core level. + /// On the client side, there should only be a single global session; multiple instances of + /// `Components` are not required-only one is necessary. + /// + /// The `Components` structure maintains the list of all available components in the system, + /// specifically parsers and data sources. It is responsible for: + /// - Providing the list of available components + /// - Providing the configuration schema for a component + /// - Validating component configuration + /// - Supporting lazy loading of "heavy" configuration fields + /// + /// Note: Some components may have configuration fields that cannot be resolved or rendered + /// immediately (e.g., DLT file statistics). Such configuration fields are referred to as **lazy**. + /// `Components` first returns a general description of the lazy field, and once the field + /// is fully resolved, it provides the full configuration metadata. + /// While waiting, the client displays all static options along with a spinner or loading indicator + /// for the lazy ones. + /// + /// The `abort` method can be used to cancel the loading of a lazy configuration field. This is + /// particularly important if the user cancels the session before it is fully initialized. + /// + /// # Arguments + /// * `callback: F` - A callback function for communicating with the TypeScript client. It allows + /// Rust code to emit events back to the frontend. + #[node_bindgen(mt)] + async fn init( + &mut self, + callback: F, + ) -> Result<(), stypes::ComputationError> { + let rt = Runtime::new().map_err(|e| { + stypes::ComputationError::Process(format!("Could not start tokio runtime: {e}")) + })?; + let (tx_session, rx_session) = oneshot::channel(); + thread::spawn(move || { + rt.block_on(async { + match SessionRegister::new().await { + Ok((session, mut rx_callback_events)) => { + if tx_session.send(Some(session)).is_err() { + error!("Cannot setup session instance"); + return; + } + debug!("task is started"); + while let Some(event) = rx_callback_events.recv().await { + callback(event) + } + debug!("sending Destroyed event"); + callback(stypes::CallbackOptionsEvent::Destroyed); + debug!("task is finished"); + } + Err(e) => { + error!("Cannot create session instance: {e:?}"); + if tx_session.send(None).is_err() { + error!("Cannot setup session instance"); + } + } + } + }) + }); + self.session = rx_session.await.map_err(|_| { + stypes::ComputationError::Communication(String::from( + "Fail to get session instance to setup", + )) + })?; + Ok(()) + } + + /// Retrieves the list of available components of a specified type. + /// + /// This method sends an asynchronous request to get the list of components, filtered by type. + /// It uses the `Api::GetComponents` command to query the system. + /// + /// # Arguments + /// + /// * `origin` - The source origin within which the components are being used. + /// * `ty` - The type of components to retrieve (e.g., parser, source). + /// + /// # Returns + /// + /// * `Ok(stypes::IdentList)` - A vector of component identifiers. + /// * `Err(stypes::ComputationError)` - An error if the request fails or the response is not received. + /// + /// # Errors + /// + /// The method returns an error if: + /// - The API request could not be sent. + /// - The response from the API could not be retrieved. + #[node_bindgen] + async fn get_components( + &self, + origin: JSArrayBuffer, + ty: JSArrayBuffer, + ) -> Result { + let ty = stypes::ComponentType::decode(&ty).map_err(stypes::ComputationError::Decoding)?; + let origin = + stypes::SessionAction::decode(&origin).map_err(stypes::ComputationError::Decoding)?; + let session = self + .session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)?; + session + .get_components(origin, ty) + .await + .map(|idents| idents.into()) + .map_err(stypes::ComputationError::NativeError) + } + + /// Checks whether a source supports the Source Data Exchange (SDE) mechanism. + /// + /// This is an asynchronous Node.js-exposed method that receives a source UUID and a serialized + /// [`SessionAction`] buffer. It decodes the session context, parses the UUID, and delegates + /// the actual check to the internal session logic. This method allows JavaScript code + /// to determine if sending data to a specific source is permitted. + /// + /// # Arguments + /// + /// * `uuid` - A string representation of the source UUID. + /// * `origin` - A JavaScript `ArrayBuffer` containing a serialized [`SessionAction`] object. + /// + /// # Returns + /// + /// A `Promise` resolving to `true` if SDE is supported, `false` otherwise. + /// + /// # Errors + /// + /// Returns a [`ComputationError`] in the following cases: + /// - If the session is not available, + /// - If decoding the `origin` buffer fails, + /// - If the UUID is invalid, + /// - If the underlying native API returns an error. + #[node_bindgen] + async fn is_sde_supported( + &self, + uuid: String, + origin: JSArrayBuffer, + ) -> Result { + let origin = + stypes::SessionAction::decode(&origin).map_err(stypes::ComputationError::Decoding)?; + let uuid = Uuid::from_str(&uuid).map_err(|_| stypes::ComputationError::InvalidData)?; + let session = self + .session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)?; + session + .is_sde_supported(uuid, origin) + .await + .map_err(stypes::ComputationError::NativeError) + } + + /// Returns a list of compatible source and parser components that can be used without configuration. + /// + /// This method is exposed to JavaScript and is typically used in "quick start" workflows, + /// such as opening a file without specifying any component settings. It decodes the session context + /// from the provided binary buffer and delegates the lookup to the internal API. + /// + /// The result includes only those components that: + /// - Are compatible with the provided session (`SessionAction`); + /// - Support default options (i.e., do not require explicit configuration); + /// - Match the inferred IO type (e.g., text vs binary). + /// + /// # Arguments + /// + /// * `origin` – A serialized [`SessionAction`] (as `ArrayBuffer` from JavaScript), + /// describing the context of the session (e.g., file path, multiple files). + /// + /// # Returns + /// + /// A `Promise` resolving to a [`ComponentsList`] object containing matching sources and parsers. + /// If no compatible components are found, the list will be empty. + /// + /// # Errors + /// + /// Returns a [`ComputationError`] in the following cases: + /// - Invalid or undecodable session data (`Decoding`); + /// - Session not initialized (`SessionUnavailable`); + /// - Internal failure in component resolution (`NativeError`). + #[node_bindgen] + async fn get_compatible_setup( + &self, + origin: JSArrayBuffer, + ) -> Result { + let origin = + stypes::SessionAction::decode(&origin).map_err(stypes::ComputationError::Decoding)?; + let session = self + .session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)?; + session + .get_compatible_setup(origin) + .await + .map_err(stypes::ComputationError::NativeError) + } + + /// Retrieves the default configuration options for a specific component, + /// if it can be used without explicit user configuration. + /// + /// This method is exposed to JavaScript and is typically used when attempting to automatically + /// initialize a source or parser. It decodes the session context and validates the component UUID, + /// then queries whether the component provides default options for that context. + /// + /// If the component requires manual configuration, the method returns an error. + /// + /// # Arguments + /// + /// * `origin` – A serialized [`SessionAction`] passed from JavaScript as `ArrayBuffer`. + /// This provides context such as the file being opened or the session type. + /// * `uuid` – The string representation of the component’s UUID. + /// + /// # Returns + /// + /// A `Promise` resolving to a [`FieldList`] object representing the component’s default settings. + /// If the component has no configurable fields, the list will be empty (`FieldList([])`). + /// + /// # Errors + /// + /// Returns a [`ComputationError`] in the following cases: + /// - Invalid or undecodable session data (`Decoding`); + /// - Malformed or invalid UUID (`InvalidData`); + /// - Session is not initialized (`SessionUnavailable`); + /// - The component does not support default options (`NativeError`). + #[node_bindgen] + async fn get_default_options( + &self, + origin: JSArrayBuffer, + uuid: String, + ) -> Result { + let origin = + stypes::SessionAction::decode(&origin).map_err(stypes::ComputationError::Decoding)?; + let uuid = Uuid::from_str(&uuid).map_err(|_| stypes::ComputationError::InvalidData)?; + let session = self + .session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)?; + session + .get_default_options(origin, uuid) + .await + .map_err(stypes::ComputationError::NativeError) + } + + /// Retrieves the configuration options for a list of components. + /// + /// This method sends an asynchronous request to obtain the settings of the specified components. + /// It uses the `Api::GetOptions` command to communicate with the underlying system. + /// + /// # Arguments + /// + /// * `targets` - A vector of UUIDs representing the components whose settings are being requested. + /// * `origin` - The source origin within which the components are being used. + /// + /// # Returns + /// + /// * `Ok(stypes::ComponentsOptionsList)` - The list of component configuration options. + /// * `Err(stypes::ComputationError)` - An error if the request fails or the response is not received. + /// + /// # Errors + /// + /// The method returns an error if: + /// - The API request could not be sent. + /// - The response from the API could not be retrieved. + #[node_bindgen] + async fn get_options( + &self, + origin: JSArrayBuffer, + targets: Vec, + ) -> Result { + let targets: Vec = targets + .into_iter() + .map(|uuid| Uuid::from_str(&uuid).map_err(|_| stypes::ComputationError::InvalidData)) + .collect::>()?; + let origin = + stypes::SessionAction::decode(&origin).map_err(stypes::ComputationError::Decoding)?; + let session = self + .session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)?; + session + .get_options(targets, origin) + .await + .map_err(stypes::ComputationError::NativeError) + } + + /// Requests the render supported by parser + /// + /// This API is used by the client to retrieve the render to use. + /// + /// # Arguments + /// + /// * `uuid` - Uuid of parser. + /// + /// # Result + /// + /// * `Result, ComputationError>` - The result containing the render or an error. + /// + /// # Note + /// If component doesn't have render, returns `None` + #[node_bindgen] + async fn get_output_render( + &self, + uuid: String, + ) -> Result, stypes::ComputationError> { + let uuid = Uuid::from_str(&uuid).map_err(|_| stypes::ComputationError::InvalidData)?; + let session = self + .session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)?; + session + .get_output_render(uuid) + .await + .map_err(stypes::ComputationError::NativeError) + } + + /// Requests the ident of component + /// + /// This API is used by the client to retrieve the identification of component parser or source. + /// + /// # Arguments + /// + /// * `Uuid` - Uuid of component (parser / source). + /// * `tx` - A one-shot sender used to deliver the result back to the client. + /// + /// # Result + /// + /// * `Result, ComputationError>` - Ident of target component. + /// + /// # Note + /// If component doesn't exist, returns `None` + #[node_bindgen] + async fn get_ident( + &self, + uuid: String, + ) -> Result, stypes::ComputationError> { + let uuid = Uuid::from_str(&uuid).map_err(|_| stypes::ComputationError::InvalidData)?; + let session = self + .session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)?; + session + .get_ident(uuid) + .await + .map_err(stypes::ComputationError::NativeError) + } + + /// Asynchronously validates the configuration of a specific component. + /// + /// This method sends a validation request to the system and waits for the response asynchronously. + /// + /// # Arguments + /// + /// * `source` - The origin type indicating the context in which the validation is performed. + /// * `target` - The identifier of the component (parser, source). + /// * `fields` - A list of configuration field values to be validated. + /// + /// # Returns + /// + /// `Result`: + /// * On success: A `HashMap` (wrapped into `stypes::FieldsValidationErrors`) where the key is the field's + /// identifier (`String`) and the value is the error message (`String`). + /// * If all fields are valid and have no errors, an empty `HashMap` is returned. + /// * On failure: A `stypes::ComputationError` indicating the reason for the validation failure. + /// + /// # Errors + /// + /// Returns an error if: + /// * The API call to validate the configuration fails to send. + /// * The response from the validation API cannot be retrieved. + #[node_bindgen] + async fn validate( + &self, + origin: JSArrayBuffer, + options: JSArrayBuffer, + ) -> Result { + let origin = + stypes::SessionAction::decode(&origin).map_err(stypes::ComputationError::Decoding)?; + let options = stypes::ComponentOptions::decode(&options) + .map_err(stypes::ComputationError::Decoding)?; + let session = self + .session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)?; + Ok(stypes::FieldsValidationErrors { + errors: session + .validate(origin, options.uuid, options.fields) + .await + .map_err(stypes::ComputationError::NativeError)?, + }) + } + + /// Aborts the lazy loading tasks for the specified fields. + /// + /// This method sends a cancellation request for ongoing lazy loading tasks associated with the given field IDs. + /// It uses the `Api::CancelLoading` command to perform the operation. + /// + /// # Arguments + /// + /// * `fields` - A vector of field IDs for which the lazy loading should be aborted. + /// + /// # Returns + /// + /// * `Ok(())` - If the cancellation request was successfully sent. + /// * `Err(stypes::ComputationError)` - An error if the API request could not be sent. + #[node_bindgen] + fn abort(&self, fields: Vec) -> Result<(), stypes::ComputationError> { + let session = self + .session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)?; + session + .abort(fields) + .map_err(stypes::ComputationError::NativeError)?; + Ok(()) + } + + /// Initiates the shutdown process for the components session. + /// + /// This method sends a shutdown command to gracefully terminate the session. + /// It uses the `Api::Shutdown` command to initiate the shutdown. + /// + /// # Returns + /// + /// * `Ok(())` - If the shutdown request was successfully sent and acknowledged. + /// * `Err(stypes::ComputationError)` - An error if the shutdown process fails. + /// + /// # Errors + /// + /// The method returns an error if: + /// - The API request could not be sent. + /// - The response from the API could not be retrieved. + #[node_bindgen] + async fn destroy(&self) -> Result<(), stypes::ComputationError> { + self.session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)? + .shutdown() + .await + .map_err(stypes::ComputationError::NativeError) + } + + #[node_bindgen] + async fn installed_plugins_list( + &self, + ) -> Result { + self.session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)? + .installed_plugins_list() + .await + .map_err(stypes::ComputationError::NativeError) + } + + #[node_bindgen] + async fn invalid_plugins_list( + &self, + ) -> Result { + self.session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)? + .invalid_plugins_list() + .await + .map_err(stypes::ComputationError::NativeError) + } + + #[node_bindgen] + async fn installed_plugins_paths( + &self, + ) -> Result { + self.session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)? + .installed_plugins_paths() + .await + .map_err(stypes::ComputationError::NativeError) + } + + #[node_bindgen] + async fn invalid_plugins_paths( + &self, + ) -> Result { + self.session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)? + .invalid_plugins_paths() + .await + .map_err(stypes::ComputationError::NativeError) + } + + #[node_bindgen] + async fn installed_plugin_info( + &self, + plugin_path: String, + ) -> Result, stypes::ComputationError> { + self.session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)? + .installed_plugin_info(plugin_path) + .await + .map_err(stypes::ComputationError::NativeError) + } + + #[node_bindgen] + async fn invalid_plugin_info( + &self, + plugin_path: String, + ) -> Result, stypes::ComputationError> { + self.session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)? + .invalid_plugin_info(plugin_path) + .await + .map_err(stypes::ComputationError::NativeError) + } + + #[node_bindgen] + async fn get_plugin_run_data( + &self, + plugin_path: String, + ) -> Result, stypes::ComputationError> { + self.session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)? + .get_plugin_run_data(plugin_path) + .await + .map_err(stypes::ComputationError::NativeError) + } + + #[node_bindgen] + async fn reload_plugins(&self) -> Result<(), stypes::ComputationError> { + self.session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)? + .reload_plugins() + .await + .map_err(stypes::ComputationError::NativeError) + } + + #[node_bindgen] + async fn add_plugin(&self, plugin_path: String) -> Result<(), stypes::ComputationError> { + self.session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)? + .add_plugin(plugin_path, None) + .await + .map_err(stypes::ComputationError::NativeError) + } + + #[node_bindgen] + async fn remove_plugin(&self, plugin_path: String) -> Result<(), stypes::ComputationError> { + self.session + .as_ref() + .ok_or(stypes::ComputationError::SessionUnavailable)? + .remove_plugin(plugin_path) + .await + .map_err(stypes::ComputationError::NativeError) + } +} diff --git a/application/apps/rustcore/rs-bindings/src/js/converting/concat.rs b/application/apps/rustcore/rs-bindings/src/js/converting/concat.rs deleted file mode 100644 index 084bb2054..000000000 --- a/application/apps/rustcore/rs-bindings/src/js/converting/concat.rs +++ /dev/null @@ -1,46 +0,0 @@ -// use merging::concatenator::ConcatenatorInput; -// use node_bindgen::{ -// core::{ -// val::{JsEnv, JsObject}, -// JSValue, NjError, -// }, -// sys::napi_value, -// }; -// use serde::Serialize; - -// #[derive(Serialize, Debug, Clone)] -// pub struct WrappedConcatenatorInput(ConcatenatorInput); - -// // impl WrappedConcatenatorInput { -// // pub fn as_concatenator_input(&self) -> ConcatenatorInput { -// // self.0.clone() -// // } -// // } - -// impl JSValue<'_> for WrappedConcatenatorInput { -// fn convert_to_rust(env: &JsEnv, n_value: napi_value) -> Result { -// if let Ok(js_obj) = env.convert_to_rust::(n_value) { -// let path: String = match js_obj.get_property("path") { -// Ok(Some(value)) => value.as_value()?, -// Ok(None) => { -// return Err(NjError::Other("[path] property is not found".to_owned())); -// } -// Err(e) => { -// return Err(e); -// } -// }; -// let tag: String = match js_obj.get_property("tag") { -// Ok(Some(value)) => value.as_value()?, -// Ok(None) => { -// return Err(NjError::Other("[tag] property is not found".to_owned())); -// } -// Err(e) => { -// return Err(e); -// } -// }; -// Ok(WrappedConcatenatorInput(ConcatenatorInput { path, tag })) -// } else { -// Err(NjError::Other("not valid format".to_owned())) -// } -// } -// } diff --git a/application/apps/rustcore/rs-bindings/src/js/converting/merge.rs b/application/apps/rustcore/rs-bindings/src/js/converting/merge.rs deleted file mode 100644 index 481d1cd49..000000000 --- a/application/apps/rustcore/rs-bindings/src/js/converting/merge.rs +++ /dev/null @@ -1,75 +0,0 @@ -use merging::merger::FileMergeOptions; -use node_bindgen::{ - core::{ - JSValue, NjError, - val::{JsEnv, JsObject}, - }, - sys::napi_value, -}; -use serde::Serialize; - -#[derive(Serialize, Debug, Clone)] -pub struct WrappedFileMergeOptions(FileMergeOptions); - -// impl WrappedFileMergeOptions { -// pub fn as_file_merge_options(&self) -> FileMergeOptions { -// self.0.clone() -// } -// } - -impl JSValue<'_> for WrappedFileMergeOptions { - fn convert_to_rust(env: &JsEnv, n_value: napi_value) -> Result { - if let Ok(js_obj) = env.convert_to_rust::(n_value) { - let path: String = match js_obj.get_property("path") { - Ok(Some(value)) => value.as_value()?, - Ok(None) => { - return Err(NjError::Other("[path] property is not found".to_owned())); - } - Err(e) => { - return Err(e); - } - }; - let tag: String = match js_obj.get_property("tag") { - Ok(Some(value)) => value.as_value()?, - Ok(None) => { - return Err(NjError::Other("[tag] property is not found".to_owned())); - } - Err(e) => { - return Err(e); - } - }; - let format: String = match js_obj.get_property("format") { - Ok(Some(value)) => value.as_value()?, - Ok(None) => { - return Err(NjError::Other("[format] property is not found".to_owned())); - } - Err(e) => { - return Err(e); - } - }; - let offset: Option = match js_obj.get_property("offset") { - Ok(Some(value)) => Some(value.as_value()?), - Ok(None) => None, - Err(e) => { - return Err(e); - } - }; - let year: Option = match js_obj.get_property("year") { - Ok(Some(value)) => Some(value.as_value()?), - Ok(None) => None, - Err(e) => { - return Err(e); - } - }; - Ok(WrappedFileMergeOptions(FileMergeOptions { - path, - offset, - year, - tag, - format, - })) - } else { - Err(NjError::Other("not valid format".to_owned())) - } - } -} diff --git a/application/apps/rustcore/rs-bindings/src/js/converting/mod.rs b/application/apps/rustcore/rs-bindings/src/js/converting/mod.rs index 3f8c5ad97..34d102d76 100644 --- a/application/apps/rustcore/rs-bindings/src/js/converting/mod.rs +++ b/application/apps/rustcore/rs-bindings/src/js/converting/mod.rs @@ -1,4 +1 @@ -// pub mod concat; pub mod filter; -pub mod merge; -pub mod source; diff --git a/application/apps/rustcore/rs-bindings/src/js/converting/source.rs b/application/apps/rustcore/rs-bindings/src/js/converting/source.rs deleted file mode 100644 index 28ce5dd87..000000000 --- a/application/apps/rustcore/rs-bindings/src/js/converting/source.rs +++ /dev/null @@ -1,20 +0,0 @@ -use node_bindgen::{ - core::{ - NjError, TryIntoJs, - val::{JsEnv, JsObject}, - }, - sys::napi_value, -}; -use serde::Serialize; - -#[derive(Serialize, Debug, Clone)] -pub struct WrappedSourceDefinition(pub stypes::SourceDefinition); - -impl TryIntoJs for WrappedSourceDefinition { - fn try_to_js(self, js_env: &JsEnv) -> Result { - let mut json = JsObject::new(*js_env, js_env.create_object()?); - json.set_property("id", js_env.create_int32(self.0.id as i32)?)?; - json.set_property("alias", js_env.create_string_utf8(&self.0.alias)?)?; - json.try_to_js(js_env) - } -} diff --git a/application/apps/rustcore/rs-bindings/src/js/jobs/mod.rs b/application/apps/rustcore/rs-bindings/src/js/jobs/mod.rs index 95c1f4bcd..73f9c9c97 100644 --- a/application/apps/rustcore/rs-bindings/src/js/jobs/mod.rs +++ b/application/apps/rustcore/rs-bindings/src/js/jobs/mod.rs @@ -220,134 +220,6 @@ impl UnboundJobs { .await } - #[node_bindgen] - async fn installed_plugins_list( - &self, - id: i64, - ) -> Result, stypes::ComputationError> { - self.api - .as_ref() - .ok_or(stypes::ComputationError::SessionUnavailable)? - .installed_plugins_list(u64_from_i64(id)?) - .await - } - - #[node_bindgen] - async fn invalid_plugins_list( - &self, - id: i64, - ) -> Result, stypes::ComputationError> { - self.api - .as_ref() - .ok_or(stypes::ComputationError::SessionUnavailable)? - .invalid_plugins_list(u64_from_i64(id)?) - .await - } - - #[node_bindgen] - async fn installed_plugins_paths( - &self, - id: i64, - ) -> Result, stypes::ComputationError> { - self.api - .as_ref() - .ok_or(stypes::ComputationError::SessionUnavailable)? - .installed_plugins_paths(u64_from_i64(id)?) - .await - } - - #[node_bindgen] - async fn invalid_plugins_paths( - &self, - id: i64, - ) -> Result, stypes::ComputationError> { - self.api - .as_ref() - .ok_or(stypes::ComputationError::SessionUnavailable)? - .invalid_plugins_paths(u64_from_i64(id)?) - .await - } - - #[node_bindgen] - async fn installed_plugin_info( - &self, - id: i64, - plugin_path: String, - ) -> Result>, stypes::ComputationError> - { - self.api - .as_ref() - .ok_or(stypes::ComputationError::SessionUnavailable)? - .installed_plugin_info(u64_from_i64(id)?, plugin_path) - .await - } - - #[node_bindgen] - async fn invalid_plugin_info( - &self, - id: i64, - plugin_path: String, - ) -> Result>, stypes::ComputationError> - { - self.api - .as_ref() - .ok_or(stypes::ComputationError::SessionUnavailable)? - .invalid_plugin_info(u64_from_i64(id)?, plugin_path) - .await - } - - #[node_bindgen] - async fn get_plugin_run_data( - &self, - id: i64, - plugin_path: String, - ) -> Result>, stypes::ComputationError> - { - self.api - .as_ref() - .ok_or(stypes::ComputationError::SessionUnavailable)? - .get_plugin_run_data(u64_from_i64(id)?, plugin_path) - .await - } - - #[node_bindgen] - async fn reload_plugins( - &self, - id: i64, - ) -> Result, stypes::ComputationError> { - self.api - .as_ref() - .ok_or(stypes::ComputationError::SessionUnavailable)? - .reload_plugins(u64_from_i64(id)?) - .await - } - - #[node_bindgen] - async fn add_plugin( - &self, - id: i64, - plugin_path: String, - ) -> Result, stypes::ComputationError> { - self.api - .as_ref() - .ok_or(stypes::ComputationError::SessionUnavailable)? - .add_plugin(u64_from_i64(id)?, plugin_path, None) - .await - } - - #[node_bindgen] - async fn remove_plugin( - &self, - id: i64, - plugin_path: String, - ) -> Result, stypes::ComputationError> { - self.api - .as_ref() - .ok_or(stypes::ComputationError::SessionUnavailable)? - .remove_plugin(u64_from_i64(id)?, plugin_path) - .await - } - #[node_bindgen] async fn job_cancel_test( &self, diff --git a/application/apps/rustcore/rs-bindings/src/js/mod.rs b/application/apps/rustcore/rs-bindings/src/js/mod.rs index 1be11b72f..eb106a0e0 100644 --- a/application/apps/rustcore/rs-bindings/src/js/mod.rs +++ b/application/apps/rustcore/rs-bindings/src/js/mod.rs @@ -1,3 +1,4 @@ +pub mod components; pub mod converting; pub mod jobs; pub mod session; diff --git a/application/apps/rustcore/rs-bindings/src/js/session/mod.rs b/application/apps/rustcore/rs-bindings/src/js/session/mod.rs index 5c078103f..649e617bd 100644 --- a/application/apps/rustcore/rs-bindings/src/js/session/mod.rs +++ b/application/apps/rustcore/rs-bindings/src/js/session/mod.rs @@ -447,7 +447,7 @@ impl RustSession { operation_id: String, ) -> Result<(), stypes::ComputationError> { let options = - stypes::ObserveOptions::decode(&options).map_err(stypes::ComputationError::Decoding)?; + stypes::SessionSetup::decode(&options).map_err(stypes::ComputationError::Decoding)?; self.session .as_ref() .ok_or(stypes::ComputationError::SessionUnavailable)? diff --git a/application/apps/rustcore/ts-bindings/package.json b/application/apps/rustcore/ts-bindings/package.json index 4b886af21..af41ccf3f 100644 --- a/application/apps/rustcore/ts-bindings/package.json +++ b/application/apps/rustcore/ts-bindings/package.json @@ -6,8 +6,9 @@ "types": "./dist/index.d.ts", "scripts": { "build": "node_modules/.bin/tsc -p tsconfig.json", + "build-jasmine": "node_modules/.bin/tsc -p ./spec/tsconfig.json", "prod": "node_modules/.bin/tsc -p tsconfig.json", - "test_cancel": "node_modules/.bin/electron node_modules/jasmine-ts/lib/index.js ./spec/session.cancel.spec.ts", + "test": "node_modules/.bin/jasmine ./spec/build/spec/session.components.spec.js", "lint": "node_modules/.bin/eslint . --max-warnings=0", "check": "node_modules/.bin/tsc -p tsconfig.json --noemit" }, diff --git a/application/apps/rustcore/ts-bindings/spec/common.ts b/application/apps/rustcore/ts-bindings/spec/common.ts index 5f6a65d2c..fb827da82 100644 --- a/application/apps/rustcore/ts-bindings/spec/common.ts +++ b/application/apps/rustcore/ts-bindings/spec/common.ts @@ -1,6 +1,6 @@ // tslint:disable -import { Jobs, Tracker, Session } from '../src/index'; +import { Jobs, Tracker, Session, Components } from '../src/index'; import { Logger } from './logger'; import { error, numToLogLevel } from 'platform/log/utils'; import { state } from 'platform/log'; @@ -70,7 +70,13 @@ export function readConfigFile(filenameEnvVar: string, defaultPaths: string[] } export function finish( - sessions: Array | Session | Jobs | Tracker | undefined, + sessions: + | Array + | Session + | Jobs + | Tracker + | Components + | undefined, done: (...args: any[]) => void, err?: Error, ): void { diff --git a/application/apps/rustcore/ts-bindings/spec/config.ts b/application/apps/rustcore/ts-bindings/spec/config.ts index 775deb23a..ea323a872 100644 --- a/application/apps/rustcore/ts-bindings/spec/config.ts +++ b/application/apps/rustcore/ts-bindings/spec/config.ts @@ -76,6 +76,9 @@ export interface IConfiguration { observing: { regular: IRegularTests; }; + components: { + regular: IRegularTests; + }; }; } diff --git a/application/apps/rustcore/ts-bindings/spec/defaults.json b/application/apps/rustcore/ts-bindings/spec/defaults.json index 03f8288e9..74b44fddd 100644 --- a/application/apps/rustcore/ts-bindings/spec/defaults.json +++ b/application/apps/rustcore/ts-bindings/spec/defaults.json @@ -285,6 +285,16 @@ }, "files": {} } + }, + "components": { + "regular": { + "execute_only": [], + "list": { + "1": "Test 1. Cancel loading options", + "2": "Test 2. Wait for lazy fields" + }, + "files": {} + } } } } diff --git a/application/apps/rustcore/ts-bindings/spec/runners.ts b/application/apps/rustcore/ts-bindings/spec/runners.ts index 161e9b0a6..ae96f7443 100644 --- a/application/apps/rustcore/ts-bindings/spec/runners.ts +++ b/application/apps/rustcore/ts-bindings/spec/runners.ts @@ -1,4 +1,12 @@ -import { Jobs, Tracker, Session, SessionStream, ISessionEvents, SessionSearch } from '../src/index'; +import { + Jobs, + Tracker, + Session, + SessionStream, + ISessionEvents, + SessionSearch, + Components, +} from '../src/index'; import { Logger, getLogger } from './logger'; import { error } from 'platform/log/utils'; import { IRegularTests } from './config'; @@ -118,11 +126,13 @@ export function unbound( test: ( logger: Logger, done: () => void, - add: ScopeInjector, + add: ScopeInjector, ) => Promise, ): Promise { - const scope: Array = []; - const injector: ScopeInjector = (obj: Session | Tracker | Jobs) => { + const scope: Array = []; + const injector: ScopeInjector = ( + obj: Session | Tracker | Jobs | Components, + ) => { scope.push(obj); return obj; }; @@ -140,4 +150,4 @@ export function unbound( finish(scope, done, new Error(error(err))); } }); -} \ No newline at end of file +} diff --git a/application/apps/rustcore/ts-bindings/spec/session.components.spec.ts b/application/apps/rustcore/ts-bindings/spec/session.components.spec.ts new file mode 100644 index 000000000..b08c1c147 --- /dev/null +++ b/application/apps/rustcore/ts-bindings/spec/session.components.spec.ts @@ -0,0 +1,146 @@ +// tslint:disable +// We need to provide path to TypeScript types definitions +/// +/// + +import { initLogger } from './logger'; +initLogger(); +import { Components } from '../src/index'; +import { readConfigurationFile } from './config'; +import { finish } from './common'; +import { SessionAction, FieldDesc, LazyFieldDesc, StaticFieldDesc } from 'platform/types/bindings'; + +import * as runners from './runners'; +import { error } from 'platform/log/utils'; +import { LoadingDoneEvent } from '../src/api/components.provider'; + +const config = readConfigurationFile().get().tests.components; + +const TCP_SOURCE_UUID: string = '05050505-0505-0505-0505-050505050505'; +const DLT_PARSER_UUID: string = '01010101-0101-0101-0101-010101010101'; +const SOMEIP_PARSER_UUID: string = '02020202-0202-0202-0202-020202020202'; + +describe('Jobs', function () { + it(config.regular.list[1], function () { + return runners.unbound(config.regular, 1, async (logger, done, collector) => { + try { + const components = collector(await Components.create()) as Components; + const origin: SessionAction = { File: 'somefile' }; + // Request available parsers and sources + let parsers = await components.get(origin).parsers(); + let sources = await components.get(origin).sources(); + if (!parsers.find((ident) => ident.uuid === DLT_PARSER_UUID)) { + return finish( + [components], + done, + new Error(`Fail to find DLT parser by uuid: ${DLT_PARSER_UUID}`), + ); + } + if (!sources.find((ident) => ident.uuid === TCP_SOURCE_UUID)) { + return finish( + [components], + done, + new Error(`Fail to find DLT parser by uuid: ${TCP_SOURCE_UUID}`), + ); + } + // Subscribe to events + let received_lazy: StaticFieldDesc[] | undefined = undefined; + components.getEvents().LoadingDone.subscribe((event: LoadingDoneEvent) => { + received_lazy = event.fields; + }); + // Request options scheme + const fields = await components.getOptions(origin, [ + TCP_SOURCE_UUID, + DLT_PARSER_UUID, + ]); + const dlt_opts: FieldDesc[] | undefined = fields.options.get(DLT_PARSER_UUID); + if (!dlt_opts) { + return finish([components], done, new Error(`No fields from DLT parser`)); + } + const lazy_field: { Lazy: LazyFieldDesc } | undefined = dlt_opts.find( + (field: FieldDesc) => 'Lazy' in field, + ); + if (!lazy_field) { + return finish([components], done, new Error(`No lazy fields from DLT parser`)); + } + // Do not wait for field + components.abort([lazy_field.Lazy.id]); + setTimeout(() => { + if (received_lazy !== undefined) { + finish( + [components], + done, + new Error( + `Lazy options received, but should not: ${JSON.stringify( + received_lazy, + )}`, + ), + ); + } else { + // We are good + finish([components], done); + } + }, 2000); + } catch (err) { + finish([], done, new Error(error(err))); + } + }); + }); + + it(config.regular.list[2], function () { + return runners.unbound(config.regular, 2, async (logger, done, collector) => { + try { + const components = collector(await Components.create()) as Components; + const origin: SessionAction = { File: 'somefile' }; + // Request available parsers and sources + let parsers = await components.get(origin).parsers(); + let sources = await components.get(origin).sources(); + if (!parsers.find((ident) => ident.uuid === SOMEIP_PARSER_UUID)) { + return finish( + [components], + done, + new Error(`Fail to find DLT parser by uuid: ${SOMEIP_PARSER_UUID}`), + ); + } + if (!sources.find((ident) => ident.uuid === TCP_SOURCE_UUID)) { + return finish( + [components], + done, + new Error(`Fail to find DLT parser by uuid: ${TCP_SOURCE_UUID}`), + ); + } + // Subscribe to events + let received_lazy: StaticFieldDesc[] | undefined = undefined; + components.getEvents().LoadingDone.subscribe((event: LoadingDoneEvent) => { + received_lazy = event.fields; + }); + // Request options scheme + const fields = await components.getOptions(origin, [ + TCP_SOURCE_UUID, + DLT_PARSER_UUID, + ]); + const dlt_opts: FieldDesc[] | undefined = fields.options.get(DLT_PARSER_UUID); + if (!dlt_opts) { + return finish([components], done, new Error(`No fields from DLT parser`)); + } + const lazy_field: { Lazy: LazyFieldDesc } | undefined = dlt_opts.find( + (field: FieldDesc) => 'Lazy' in field, + ); + if (!lazy_field) { + return finish([components], done, new Error(`No lazy fields from DLT parser`)); + } + setTimeout(() => { + if (received_lazy === undefined) { + // We are good + finish([components], done); + } else { + // We didn't get lazy options + finish([components], done, new Error(`Lazy options not received`)); + } + }, 1000); + } catch (err) { + finish([], done, new Error(error(err))); + } + }); + }); +}); diff --git a/application/apps/rustcore/ts-bindings/spec/session.protocol.spec.ts b/application/apps/rustcore/ts-bindings/spec/session.protocol.spec.ts index 002da5e7d..6efeb034e 100644 --- a/application/apps/rustcore/ts-bindings/spec/session.protocol.spec.ts +++ b/application/apps/rustcore/ts-bindings/spec/session.protocol.spec.ts @@ -134,13 +134,6 @@ const MAP: { [key: string]: (buf: Uint8Array) => any } = { PluginsList: protocol.decodePluginsList, InvalidPluginsList: protocol.decodeInvalidPluginsList, PluginsPathsList: protocol.decodePluginsPathsList, - CommandOutcome_PluginsList: protocol.decodeCommandOutcomeWithPluginsList, - CommandOutcome_InvalidPluginsList: protocol.decodeCommandOutcomeWithInvalidPluginsList, - CommandOutcome_PluginsPathsList: protocol.decodeCommandOutcomeWithPluginsPathsList, - CommandOutcome_Option_PluginEntity: protocol.decodeCommandOutcomeWithOptionPluginEntity, - CommandOutcome_Option_InvalidPluginEntity: - protocol.decodeCommandOutcomeWithOptionInvalidPluginEntity, - CommandOutcome_Option_PluginRunData: protocol.decodeCommandOutcomeWithOptionPluginRunData, }; const OUTPUT_PATH_ENVVAR = 'CHIPMUNK_PROTOCOL_TEST_OUTPUT'; diff --git a/application/apps/rustcore/ts-bindings/src/api/components.provider.ts b/application/apps/rustcore/ts-bindings/src/api/components.provider.ts new file mode 100644 index 000000000..088b435e5 --- /dev/null +++ b/application/apps/rustcore/ts-bindings/src/api/components.provider.ts @@ -0,0 +1,63 @@ +import { Subject } from 'platform/env/subscription'; +import { Computation } from '../provider/provider'; +import { + LoadingDoneEvent, + LoadingErrorsEvent, + LoadingErrorEvent, + LoadingCancelledEvent, +} from 'platform/types/components'; + +import * as protocol from 'protocol'; + +export interface IComponentsEvents { + LoadingDone: Subject; + LoadingErrors: Subject; + LoadingError: Subject; + LoadingCancelled: Subject; + Destroyed: Subject; +} + +export interface IComponentsEventsSignatures { + LoadingDone: 'LoadingDone'; + LoadingErrors: 'LoadingErrors'; + LoadingError: 'LoadingError'; + LoadingCancelled: 'LoadingCancelled'; + Destroyed: 'Destroyed'; +} + +const SessionEventsSignatures: IComponentsEventsSignatures = { + LoadingDone: 'LoadingDone', + LoadingErrors: 'LoadingErrors', + LoadingError: 'LoadingError', + LoadingCancelled: 'LoadingCancelled', + Destroyed: 'Destroyed', +}; + +export class ComponentsEventProvider extends Computation< + IComponentsEvents, + IComponentsEventsSignatures +> { + private readonly _events: IComponentsEvents = { + LoadingDone: new Subject(), + LoadingErrors: new Subject(), + LoadingError: new Subject(), + LoadingCancelled: new Subject(), + Destroyed: new Subject(), + }; + + constructor(uuid: string) { + super(uuid, protocol.decodeCallbackOptionsEvent); + } + + public getName(): string { + return 'ComponentsEventProvider'; + } + + public getEvents(): IComponentsEvents { + return this._events; + } + + public getEventsSignatures(): IComponentsEventsSignatures { + return SessionEventsSignatures; + } +} diff --git a/application/apps/rustcore/ts-bindings/src/api/components.ts b/application/apps/rustcore/ts-bindings/src/api/components.ts new file mode 100644 index 000000000..863d3819e --- /dev/null +++ b/application/apps/rustcore/ts-bindings/src/api/components.ts @@ -0,0 +1,165 @@ +import { Logger } from 'platform/log'; +import { scope } from 'platform/env/scope'; +import { v4 as uuid } from 'uuid'; +import { Base as Native } from '../native/native.components'; +import { ComponentsEventProvider, IComponentsEvents } from '../api/components.provider'; +import { Subscriber } from 'platform/env/subscription'; +import { + ComponentsList, + ComponentsOptionsList, + Field, + FieldDesc, + FieldList, + FieldsValidationErrors, + Ident, + OutputRender, + SessionAction, +} from 'platform/types/bindings'; + +import { + InvalidPluginEntity, + InvalidPluginsList, + PluginEntity, + PluginsList, + PluginsPathsList, + PluginRunData, +} from 'platform/types/bindings/plugins'; + +export class Components extends Subscriber { + private readonly native: Native; + private readonly provider: ComponentsEventProvider; + private readonly logger: Logger; + private readonly uuid: string = uuid(); + + constructor(resolver: (err: Error | undefined) => void) { + super(); + this.logger = scope.getLogger(`Components: ${this.uuid}`); + this.provider = new ComponentsEventProvider(this.uuid); + this.native = new Native(this.provider, (err: Error | undefined) => { + if (err instanceof Error) { + this.logger.error(`Fail to create a components session: ${err.message}`); + } + resolver(err); + }); + } + + static create(): Promise { + return new Promise((resolve, reject) => { + const components = new Components((err: Error | undefined) => { + if (err instanceof Error) { + reject(err); + } else { + resolve(components); + } + }); + }); + } + + public destroy(): Promise { + return this.native.destroy().catch((err: Error) => { + this.logger.error(`Fail to destroy session: ${err.message}`); + return Promise.reject(err); + }); + } + + public getEvents(): IComponentsEvents { + if (this.provider === undefined) { + throw new Error(`EventProvider wasn't created`); + } + return this.provider.getEvents(); + } + + public get(origin: SessionAction): { + sources(): Promise; + parsers(): Promise; + } { + return { + sources: (): Promise => { + return this.native.getComponents(origin, 'Source'); + }, + parsers: (): Promise => { + return this.native.getComponents(origin, 'Parser'); + }, + }; + } + + public getOptions(origin: SessionAction, targets: string[]): Promise> { + return this.native + .getOptions(origin, targets) + .then((list: ComponentsOptionsList) => list.options); + } + + public getOutputRender(uuid: string): Promise { + return this.native.getOutputRender(uuid); + } + + public isSdeSupported(uuid: String, origin: SessionAction): Promise { + return this.native.isSdeSupported(uuid, origin); + } + + public getCompatibleSetup(origin: SessionAction): Promise { + return this.native.getCompatibleSetup(origin); + } + + public getDefaultOptions(origin: SessionAction, target: string): Promise { + return this.native.getDefaultOptions(origin, target); + } + + public getIdent(uuid: string): Promise { + return this.native.getIdent(uuid); + } + + public validate( + origin: SessionAction, + target: string, + fields: Field[], + ): Promise> { + return this.native + .validate(origin, target, fields) + .then((list: FieldsValidationErrors) => list.errors); + } + + public abort(fields: string[]): Error | undefined { + return this.native.abort(fields); + } + + public installedPluginsList(): Promise { + return this.native.installedPluginsList(); + } + + public invalidPluginsList(): Promise { + return this.native.invalidPluginsList(); + } + + public installedPluginsPaths(): Promise { + return this.native.installedPluginsPaths(); + } + + public invalidPluginsPaths(): Promise { + return this.native.invalidPluginsPaths(); + } + + public installedPluginsInfo(plugin_path: string): Promise { + return this.native.installedPluginsInfo(plugin_path); + } + + public invalidPluginsInfo(plugin_path: string): Promise { + return this.native.invalidPluginsInfo(plugin_path); + } + + public getPluginRunData(plugin_path: string): Promise { + return this.native.getPluginRunData(plugin_path); + } + + public reloadPlugins(): Promise { + return this.native.reloadPlugins(); + } + + public addPlugin(plugin_path: string): Promise { + return this.native.addPlugin(plugin_path); + } + + public removePlugin(plugin_path: string): Promise { + return this.native.removePlugin(plugin_path); + } +} diff --git a/application/apps/rustcore/ts-bindings/src/api/executors/session.stream.observe.executor.ts b/application/apps/rustcore/ts-bindings/src/api/executors/session.stream.observe.executor.ts index 86253a1d4..d20e43d9e 100644 --- a/application/apps/rustcore/ts-bindings/src/api/executors/session.stream.observe.executor.ts +++ b/application/apps/rustcore/ts-bindings/src/api/executors/session.stream.observe.executor.ts @@ -1,7 +1,7 @@ import { TExecutor, Logger, CancelablePromise, AsyncVoidConfirmedExecutor } from './executor'; import { RustSession } from '../../native/native.session'; import { EventProvider } from '../../api/session.provider'; -import { IObserve } from 'platform/types/observe'; +import { SessionSetup } from 'platform/types/bindings'; export interface IFileOptionsDLT {} @@ -12,19 +12,23 @@ export enum EFileOptionsRequirements { export type TFileOptions = IFileOptionsDLT | undefined; -export const executor: TExecutor = ( +export const executor: TExecutor = ( session: RustSession, provider: EventProvider, logger: Logger, - options: IObserve, + options: SessionSetup, ): CancelablePromise => { const debugInfo = JSON.stringify(options); - return AsyncVoidConfirmedExecutor( + return AsyncVoidConfirmedExecutor( session, provider, logger, options, - function (session: RustSession, options: IObserve, operationUuid: string): Promise { + function ( + session: RustSession, + options: SessionSetup, + operationUuid: string, + ): Promise { return session.observe(options, operationUuid); }, (): string => { diff --git a/application/apps/rustcore/ts-bindings/src/api/jobs.ts b/application/apps/rustcore/ts-bindings/src/api/jobs.ts index 1c9da5563..93d36de17 100644 --- a/application/apps/rustcore/ts-bindings/src/api/jobs.ts +++ b/application/apps/rustcore/ts-bindings/src/api/jobs.ts @@ -2,7 +2,6 @@ import { CancelablePromise } from 'platform/env/promise'; import { Base, Cancelled, decode } from '../native/native.jobs'; import { error } from 'platform/log/utils'; import { IFilter } from 'platform/types/filter'; -import { SomeipStatistic } from 'platform/types/observe/parser/someip'; import { FoldersScanningResult, DltStatisticInfo, @@ -10,14 +9,6 @@ import { ProfileList, MapKeyValue, } from 'platform/types/bindings'; -import { - InvalidPluginEntity, - InvalidPluginsList, - PluginEntity, - PluginsList, - PluginsPathsList, - PluginRunData, -} from 'platform/types/bindings/plugins'; import * as protocol from 'protocol'; @@ -140,27 +131,6 @@ export class Jobs extends Base { return job; } - public getSomeipStatistic(paths: string[]): CancelablePromise { - const sequence = this.sequence(); - const job: CancelablePromise = this.execute( - (buf: Uint8Array): any | Error => { - const decoded = decode(buf, protocol.decodeCommandOutcomeWithString); - if (decoded instanceof Error) { - return decoded; - } - try { - return JSON.parse(decoded) as SomeipStatistic; - } catch (e) { - return new Error(error(e)); - } - }, - this.native.getSomeipStatistic(sequence, paths), - sequence, - 'getSomeipStatistic', - ); - return job; - } - public getShellProfiles(): CancelablePromise { const sequence = this.sequence(); const job: CancelablePromise = this.execute( @@ -248,166 +218,4 @@ export class Jobs extends Base { ); return job; } - - public installedPluginsList(): CancelablePromise { - const sequence = this.sequence(); - const job: CancelablePromise = this.execute( - (buf: Uint8Array): PluginEntity[] | Error => { - const decoded = decode( - buf, - protocol.decodeCommandOutcomeWithPluginsList, - ); - return decoded; - }, - this.native.installedPluginsList(sequence), - sequence, - 'installedPluginsList', - ); - return job; - } - - public invalidPluginsList(): CancelablePromise { - const sequence = this.sequence(); - const job: CancelablePromise = this.execute( - (buf: Uint8Array): InvalidPluginEntity[] | Error => { - const decoded = decode( - buf, - protocol.decodeCommandOutcomeWithInvalidPluginsList, - ); - return decoded; - }, - this.native.invalidPluginsList(sequence), - sequence, - 'invalidPluginsList', - ); - return job; - } - - public installedPluginsPaths(): CancelablePromise { - const sequence = this.sequence(); - const job: CancelablePromise = this.execute( - (buf: Uint8Array): string[] | Error => { - const decoded = decode( - buf, - protocol.decodeCommandOutcomeWithPluginsPathsList, - ); - return decoded; - }, - this.native.installedPluginsPaths(sequence), - sequence, - 'installedPluginsPaths', - ); - return job; - } - - public invalidPluginsPaths(): CancelablePromise { - const sequence = this.sequence(); - const job: CancelablePromise = this.execute( - (buf: Uint8Array): string[] | Error => { - const decoded = decode( - buf, - protocol.decodeCommandOutcomeWithPluginsPathsList, - ); - return decoded; - }, - this.native.invalidPluginsPaths(sequence), - sequence, - 'invalidPluginsPaths', - ); - return job; - } - - public installedPluginsInfo(plugin_path: string): CancelablePromise { - const sequence = this.sequence(); - const job: CancelablePromise = this.execute( - (buf: Uint8Array): PluginEntity | undefined | Error => { - const decoded = decode( - buf, - protocol.decodeCommandOutcomeWithOptionPluginEntity, - ); - return decoded; - }, - this.native.installedPluginsInfo(sequence, plugin_path), - sequence, - 'installedPluginsInfo', - ); - return job; - } - - public invalidPluginsInfo( - plugin_path: string, - ): CancelablePromise { - const sequence = this.sequence(); - const job: CancelablePromise = this.execute( - (buf: Uint8Array): InvalidPluginEntity | undefined | Error => { - const decoded = decode( - buf, - protocol.decodeCommandOutcomeWithOptionInvalidPluginEntity, - ); - return decoded; - }, - this.native.invalidPluginsInfo(sequence, plugin_path), - sequence, - 'invalidPluginsInfo', - ); - return job; - } - - public getPluginRunData(plugin_path: string): CancelablePromise { - const sequence = this.sequence(); - const job: CancelablePromise = this.execute( - (buf: Uint8Array): PluginRunData | undefined | Error => { - const decoded = decode( - buf, - protocol.decodeCommandOutcomeWithOptionPluginRunData, - ); - return decoded; - }, - this.native.getPluginRunData(sequence, plugin_path), - sequence, - 'getPluginRunData', - ); - return job; - } - - public reloadPlugins(): CancelablePromise { - const sequence = this.sequence(); - const job: CancelablePromise = this.execute( - (buf: Uint8Array): any | Error => { - return decode(buf, protocol.decodeCommandOutcomeWithVoid); - }, - this.native.reloadPlugins(sequence), - sequence, - 'reloadPlugins', - ); - return job; - } - - public addPlugin(plugin_path: string): CancelablePromise { - const sequence = this.sequence(); - const job: CancelablePromise = this.execute( - (buf: Uint8Array): any | Error => { - return decode(buf, protocol.decodeCommandOutcomeWithVoid); - }, - this.native.addPlugin(sequence, plugin_path), - sequence, - 'addPlugin', - ); - - return job; - } - - public removePlugin(plugin_path: string): CancelablePromise { - const sequence = this.sequence(); - const job: CancelablePromise = this.execute( - (buf: Uint8Array): any | Error => { - return decode(buf, protocol.decodeCommandOutcomeWithVoid); - }, - this.native.removePlugin(sequence, plugin_path), - sequence, - `removePlugin`, - ); - - return job; - } } diff --git a/application/apps/rustcore/ts-bindings/src/api/session.provider.ts b/application/apps/rustcore/ts-bindings/src/api/session.provider.ts index e561b8cc9..8da4ba775 100644 --- a/application/apps/rustcore/ts-bindings/src/api/session.provider.ts +++ b/application/apps/rustcore/ts-bindings/src/api/session.provider.ts @@ -3,7 +3,7 @@ import { ISearchUpdated } from 'platform/types/filter'; import { Computation } from '../provider/provider'; import { EErrorKind, EErrorSeverity } from '../provider/provider.errors'; import { IMapEntity, IMatchEntity, FilterMatch } from 'platform/types/filter'; -import { AttachmentInfo } from 'platform/types/bindings'; +import { AttachmentInfo, SessionDescriptor } from 'platform/types/bindings'; import * as protocol from 'protocol'; @@ -50,6 +50,11 @@ export interface IAttachmentsUpdatedUpdated { attachment: AttachmentInfo; } +export interface SessionDescriptorEvent { + uuid: string; + desc: SessionDescriptor; +} + export interface ISessionEvents { StreamUpdated: Subject; FileRead: Subject; @@ -64,13 +69,12 @@ export interface ISessionEvents { SessionError: Subject; OperationError: Subject; SessionDestroyed: Subject; + SessionDescriptor: Subject; OperationStarted: Subject; OperationProcessing: Subject; OperationDone: Subject; } -export interface ISessionEventsConvertors {} - interface ISessionEventsSignatures { StreamUpdated: 'StreamUpdated'; FileRead: 'FileRead'; @@ -86,6 +90,7 @@ interface ISessionEventsSignatures { OperationError: 'OperationError'; SessionDestroyed: 'SessionDestroyed'; OperationStarted: 'OperationStarted'; + SessionDescriptor: 'SessionDescriptor'; OperationProcessing: 'OperationProcessing'; OperationDone: 'OperationDone'; } @@ -105,75 +110,12 @@ const SessionEventsSignatures: ISessionEventsSignatures = { OperationError: 'OperationError', SessionDestroyed: 'SessionDestroyed', OperationStarted: 'OperationStarted', + SessionDescriptor: 'SessionDescriptor', OperationProcessing: 'OperationProcessing', OperationDone: 'OperationDone', }; -interface ISessionEventsInterfaces { - StreamUpdated: { self: 'number' }; - FileRead: { self: null }; - SearchUpdated: { self: 'object'; found: 'number'; stat: typeof Map }; - SearchValuesUpdated: { self: ['object', null] }; - SearchMapUpdated: { self: [typeof Array, null] }; - MapUpdated: { self: 'object'; map: typeof Array }; - IndexedMapUpdated: { self: 'object'; len: 'number' }; - MatchesUpdated: { self: 'object'; matches: typeof Array }; - Progress: { - self: 'object'; - uuid: 'string'; - progress: [ - { self: 'object'; total: 'number'; count: 'number' }, - { self: 'object'; type: 'string' }, - ]; - }; - AttachmentsUpdated: { self: 'object'; len: 'number'; attachment: typeof Object }; - SessionError: { self: 'object'; severity: 'string'; message: 'string'; kind: 'string' }; - OperationError: { - self: 'object'; - uuid: 'string'; - error: { self: 'object'; severity: 'string'; message: 'string'; kind: 'string' }; - }; - SessionDestroyed: { self: null }; - OperationStarted: { self: 'string' }; - OperationProcessing: { self: 'string' }; - OperationDone: { self: 'object'; uuid: 'string'; result: 'any' }; -} - -const SessionEventsInterfaces: ISessionEventsInterfaces = { - StreamUpdated: { self: 'number' }, - FileRead: { self: null }, - SearchUpdated: { self: 'object', found: 'number', stat: Map }, - SearchValuesUpdated: { self: ['object', null] }, - SearchMapUpdated: { self: [Array, null] }, - MapUpdated: { self: 'object', map: Array }, - IndexedMapUpdated: { self: 'object', len: 'number' }, - MatchesUpdated: { self: 'object', matches: Array }, - Progress: { - self: 'object', - uuid: 'string', - progress: [ - { self: 'object', total: 'number', count: 'number' }, - { self: 'object', type: 'string' }, - ], - }, - AttachmentsUpdated: { self: 'object', len: 'number', attachment: Object }, - SessionError: { self: 'object', severity: 'string', message: 'string', kind: 'string' }, - OperationError: { - self: 'object', - uuid: 'string', - error: { self: 'object', severity: 'string', message: 'string', kind: 'string' }, - }, - SessionDestroyed: { self: null }, - OperationStarted: { self: 'string' }, - OperationProcessing: { self: 'string' }, - OperationDone: { self: 'object', uuid: 'string', result: 'any' }, -}; - -export class EventProvider extends Computation< - ISessionEvents, - ISessionEventsSignatures, - ISessionEventsInterfaces -> { +export class EventProvider extends Computation { private readonly _events: ISessionEvents = { StreamUpdated: new Subject(), FileRead: new Subject(), @@ -190,11 +132,10 @@ export class EventProvider extends Computation< SessionDestroyed: new Subject(), OperationStarted: new Subject(), OperationProcessing: new Subject(), + SessionDescriptor: new Subject(), OperationDone: new Subject(), }; - private readonly _convertors: ISessionEventsConvertors = {}; - constructor(uuid: string) { super(uuid, protocol.decodeCallbackEvent); } @@ -210,17 +151,4 @@ export class EventProvider extends Computation< public getEventsSignatures(): ISessionEventsSignatures { return SessionEventsSignatures; } - - public getEventsInterfaces(): ISessionEventsInterfaces { - return SessionEventsInterfaces; - } - - public getConvertor(event: keyof ISessionEventsSignatures, data: T): T | O | Error { - const convertors = this._convertors as unknown as { [key: string]: (data: T) => T | O }; - if (typeof convertors[event] !== 'function') { - return data; - } else { - return convertors[event](data); - } - } } diff --git a/application/apps/rustcore/ts-bindings/src/api/session.stream.ts b/application/apps/rustcore/ts-bindings/src/api/session.stream.ts index ec67b4fa2..fa8d74858 100644 --- a/application/apps/rustcore/ts-bindings/src/api/session.stream.ts +++ b/application/apps/rustcore/ts-bindings/src/api/session.stream.ts @@ -6,11 +6,10 @@ import { SdeRequest, SdeResponse } from 'platform/types/sde'; import { EventProvider } from '../api/session.provider'; import { Executors } from './executors/session.stream.executors'; import { EFileOptionsRequirements } from './executors/session.stream.observe.executor'; -import { GrabbedElement } from 'platform/types/bindings/miscellaneous'; +import { GrabbedElement, SourceDefinition } from 'platform/types/bindings/miscellaneous'; import { IRange } from 'platform/types/range'; -import { ISourceLink } from 'platform/types/observe/types'; import { Attachment, IndexingMode } from 'platform/types/content'; -import { IObserve } from 'platform/types/observe'; +import { SessionSetup } from 'platform/types/bindings'; import { TextExportOptions } from 'platform/types/exporting'; export class SessionStream { @@ -77,11 +76,11 @@ export class SessionStream { return this._session.getFileOptionsRequirements(filename); } - public getSourcesDefinitions(): Promise { + public getSourcesDefinitions(): Promise { return this._session.getSourcesDefinitions(); } - public observe(source: IObserve): ICancelablePromise { + public observe(source: SessionSetup): ICancelablePromise { return Executors.observe(this._session, this._provider, this._logger, source); } diff --git a/application/apps/rustcore/ts-bindings/src/api/session.ts b/application/apps/rustcore/ts-bindings/src/api/session.ts index 0a7dededa..8820c1820 100644 --- a/application/apps/rustcore/ts-bindings/src/api/session.ts +++ b/application/apps/rustcore/ts-bindings/src/api/session.ts @@ -20,10 +20,8 @@ export { IEventMatchesUpdated, IEventIndexedMapUpdated, } from '../api/session.provider'; -export { EventProvider, SessionStream, SessionSearch }; -export * as $ from 'platform/types/observe'; -export * as Factory from 'platform/types/observe/factory'; +export { EventProvider, SessionStream, SessionSearch }; enum ESessionState { destroyed, diff --git a/application/apps/rustcore/ts-bindings/src/api/tracker.provider.ts b/application/apps/rustcore/ts-bindings/src/api/tracker.provider.ts index 84def0887..cad92423f 100644 --- a/application/apps/rustcore/ts-bindings/src/api/tracker.provider.ts +++ b/application/apps/rustcore/ts-bindings/src/api/tracker.provider.ts @@ -36,37 +36,7 @@ const SessionEventsSignatures: ISessionEventsSignatures = { Ticks: 'Ticks', }; -interface ISessionEventsInterfaces { - Started: { self: 'object'; uuid: 'string'; alias: 'string' }; - Stopped: { self: 'string' }; - Ticks: { - self: 'object'; - uuid: 'string'; - progress: [ - { self: 'object'; total: 'number'; count: 'number' }, - { self: 'object'; type: 'string' }, - ]; - }; -} - -const SessionEventsInterfaces: ISessionEventsInterfaces = { - Started: { self: 'object', uuid: 'string', alias: 'string' }, - Stopped: { self: 'string' }, - Ticks: { - self: 'object', - uuid: 'string', - progress: [ - { self: 'object', total: 'number', count: 'number' }, - { self: 'object', type: 'string' }, - ], - }, -}; - -export class EventProvider extends Computation< - ISessionEvents, - ISessionEventsSignatures, - ISessionEventsInterfaces -> { +export class EventProvider extends Computation { private readonly _events: ISessionEvents = { Started: new Subject(), Stopped: new Subject(), @@ -90,17 +60,4 @@ export class EventProvider extends Computation< public getEventsSignatures(): ISessionEventsSignatures { return SessionEventsSignatures; } - - public getEventsInterfaces(): ISessionEventsInterfaces { - return SessionEventsInterfaces; - } - - public getConvertor(event: keyof ISessionEventsSignatures, data: T): T | O | Error { - const convertors = this._convertors as unknown as { [key: string]: (data: T) => T | O }; - if (typeof convertors[event] !== 'function') { - return data; - } else { - return convertors[event](data); - } - } } diff --git a/application/apps/rustcore/ts-bindings/src/index.ts b/application/apps/rustcore/ts-bindings/src/index.ts index da344f180..0be61cd1b 100644 --- a/application/apps/rustcore/ts-bindings/src/index.ts +++ b/application/apps/rustcore/ts-bindings/src/index.ts @@ -24,8 +24,10 @@ export { } from './api/session'; export { Jobs } from './api/jobs'; export { Tracker } from './api/tracker'; +export { Components } from './api/components'; export { Units, Events, Interfaces }; -export * as $ from 'platform/types/observe'; +export * as Bindings from 'platform/types/bindings'; +export * as Tys from 'platform/types'; setUuidGenerator(v4); diff --git a/application/apps/rustcore/ts-bindings/src/interfaces/errors.ts b/application/apps/rustcore/ts-bindings/src/interfaces/errors.ts index 9d5dc0583..4fc906d58 100644 --- a/application/apps/rustcore/ts-bindings/src/interfaces/errors.ts +++ b/application/apps/rustcore/ts-bindings/src/interfaces/errors.ts @@ -73,7 +73,13 @@ export enum Source { SetIndexingMode = 'SetIndexingMode', GetIndexedLen = 'GetIndexedLen', getAroundIndexes = 'getAroundIndexes', + ComponentsOptions = 'ComponentsOptions', + ComponentsList = 'ComponentsList', + GetIdent = 'GetIdent', + ComponentsValidate = 'ComponentsValidate', + IdentList = 'IdentList', Native = 'Native', + Plugins = 'Plugins', Other = 'Other', } diff --git a/application/apps/rustcore/ts-bindings/src/native/native.components.ts b/application/apps/rustcore/ts-bindings/src/native/native.components.ts new file mode 100644 index 000000000..f7d0cb779 --- /dev/null +++ b/application/apps/rustcore/ts-bindings/src/native/native.components.ts @@ -0,0 +1,641 @@ +import { scope } from 'platform/env/scope'; +import { getNativeModule } from '../native/native'; +import { Type, Source, NativeError } from '../interfaces/errors'; +import { + SessionAction, + IdentList, + ComponentsOptionsList, + ComponentType, + Field, + ComponentOptions, + FieldsValidationErrors, + OutputRender, + Ident, + ComponentsList, + FieldList, +} from 'platform/types/bindings'; +import { Logger, utils } from 'platform/log'; +import { TEventEmitter } from '../provider/provider.general'; +import { Computation } from '../provider/provider'; +import { Subscriber } from 'platform/env/subscription'; +import { IComponentsEvents, IComponentsEventsSignatures } from '../api/components.provider'; + +import { + InvalidPluginEntity, + InvalidPluginsList, + PluginEntity, + PluginsList, + PluginsPathsList, + PluginRunData, +} from 'platform/types/bindings/plugins'; + +import * as protocol from 'protocol'; + +export abstract class ComponentsNative { + public abstract init(callback: TEventEmitter): Promise; + + public abstract destroy(): Promise; + + public abstract getComponents(origin: Uint8Array, ty: Uint8Array): Promise; + + public abstract isSdeSupported(uuid: String, origin: Uint8Array): Promise; + + public abstract getCompatibleSetup(origin: Uint8Array): Promise; + + public abstract getDefaultOptions(origin: Uint8Array, target: string): Promise; + + public abstract getOptions(origin: Uint8Array, targets: string[]): Promise; + + public abstract getOutputRender(uuid: String): Promise; + + public abstract getIdent(uuid: String): Promise; + + public abstract validate(origin: Uint8Array, fields: Uint8Array): Promise; + + public abstract abort(fields: string[]): void; + + public abstract installedPluginsList(): Promise; + + public abstract invalidPluginsList(): Promise; + + public abstract installedPluginsPaths(): Promise; + + public abstract invalidPluginsPaths(): Promise; + + public abstract installedPluginsInfo(plugin_path: string): Promise; + + public abstract invalidPluginsInfo(plugin_path: string): Promise; + + public abstract getPluginRunData(plugin_path: string): Promise; + + public abstract reloadPlugins(): Promise; + + public abstract addPlugin(plugin_path: string): Promise; + + public abstract removePlugin(plugin_path: string): Promise; +} + +const DESTROY_TIMEOUT = 5000; + +enum State { + Destroyed, + Destroying, + Available, + Created, + Unavailable, +} + +type DestroyResolver = () => void; + +export class Base extends Subscriber { + protected readonly logger: Logger = scope.getLogger(`Components`); + protected readonly native: ComponentsNative; + protected readonly provider: Computation; + private state: State = State.Created; + private destroyResolver: DestroyResolver | undefined; + + constructor( + provider: Computation, + resolver: (err: Error | undefined) => void, + ) { + super(); + this.native = new (getNativeModule().Components)() as ComponentsNative; + this.logger.debug(`Rust Components native session is created`); + this.provider = provider; + this.register( + this.provider.getEvents().Destroyed.subscribe(() => { + this.state = State.Destroyed; + if (this.destroyResolver === undefined) { + this.logger.error(`Session has been destroyed before call of "destroy"`); + this.state = State.Unavailable; + return; + } + this.logger.debug(`Destroy session confirmation has been gotten`); + // Confirm destroying + this.destroyResolver(); + // Shutdown provider to drop all subscriptions + this.provider.destroy(); + }), + ); + this.native + .init(provider.getEmitter()) + .then(() => { + this.logger.debug(`Rust Components native session is inited`); + this.state = State.Available; + resolver(undefined); + }) + .catch((err: Error) => { + this.logger.error( + `Fail to init Components session: ${err instanceof Error ? err.message : err}`, + ); + resolver(err); + }); + } + + public destroy(): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + this.state = State.Destroying; + return new Promise((resolve, reject) => { + this.destroyResolver = () => { + clearTimeout(timeout); + resolve(); + }; + const timeout = setTimeout(() => { + this.state = State.Unavailable; + reject( + new Error( + this.logger.error( + `Timeout error. Session wasn't closed in ${ + DESTROY_TIMEOUT / 1000 + } sec.`, + ), + ), + ); + }, DESTROY_TIMEOUT); + this.native + .destroy() + .then(() => { + this.logger.debug( + `Signal to destroy session has been sent. Wait for confirmation`, + ); + }) + .catch((err: Error) => { + this.logger.error( + `Fail to send destroy signal due error: ${ + err instanceof Error ? err.message : err + }`, + ); + this.state = State.Unavailable; + reject(err); + }); + }); + } + + public getComponents(origin: SessionAction, ty: ComponentType): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + return new Promise((resolve, reject) => { + this.native + .getComponents( + protocol.encodeSessionAction(origin), + protocol.encodeComponentType(ty), + ) + .then((buf: Uint8Array) => { + try { + resolve(protocol.decodeIdentList(buf)); + } catch (err) { + reject( + new NativeError( + new Error( + this.logger.error( + `Fail to decode message: ${utils.error(err)}`, + ), + ), + Type.InvalidOutput, + Source.IdentList, + ), + ); + } + }); + }); + } + + public isSdeSupported(uuid: String, origin: SessionAction): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + return this.native.isSdeSupported(uuid, protocol.encodeSessionAction(origin)); + } + + public getCompatibleSetup(origin: SessionAction): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + return new Promise((resolve, reject) => { + this.native; + this.native + .getCompatibleSetup(protocol.encodeSessionAction(origin)) + .then((buf: Uint8Array) => { + try { + resolve(protocol.decodeComponentsList(buf)); + } catch (err) { + reject( + new NativeError( + new Error( + this.logger.error( + `Fail to decode message: ${utils.error(err)}`, + ), + ), + Type.InvalidOutput, + Source.ComponentsList, + ), + ); + } + }); + }); + } + + public getDefaultOptions(origin: SessionAction, target: string): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + return new Promise((resolve, reject) => { + this.native; + this.native + .getDefaultOptions(protocol.encodeSessionAction(origin), target) + .then((buf: Uint8Array) => { + try { + resolve(protocol.decodeFieldList(buf)); + } catch (err) { + reject( + new NativeError( + new Error( + this.logger.error( + `Fail to decode message: ${utils.error(err)}`, + ), + ), + Type.InvalidOutput, + Source.ComponentsList, + ), + ); + } + }); + }); + } + + public getOptions(origin: SessionAction, targets: string[]): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + return new Promise((resolve, reject) => { + this.native + .getOptions(protocol.encodeSessionAction(origin), targets) + .then((buf: Uint8Array) => { + try { + resolve(protocol.decodeComponentsOptionsList(buf)); + } catch (err) { + reject( + new NativeError( + new Error( + this.logger.error( + `Fail to decode message: ${utils.error(err)}`, + ), + ), + Type.InvalidOutput, + Source.ComponentsOptions, + ), + ); + } + }); + }); + } + + public getOutputRender(uuid: String): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + return new Promise((resolve, reject) => { + this.native.getOutputRender(uuid).then((buf: Uint8Array | null) => { + if (!buf) { + return resolve(null); + } + try { + resolve(protocol.decodeOutputRender(buf)); + } catch (err) { + reject( + new NativeError( + new Error( + this.logger.error(`Fail to decode message: ${utils.error(err)}`), + ), + Type.InvalidOutput, + Source.ComponentsOptions, + ), + ); + } + }); + }); + } + + public getIdent(uuid: String): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + return new Promise((resolve, reject) => { + this.native.getIdent(uuid).then((buf: Uint8Array | null) => { + if (!buf) { + return resolve(null); + } + try { + resolve(protocol.decodeIdent(buf)); + } catch (err) { + reject( + new NativeError( + new Error( + this.logger.error(`Fail to decode message: ${utils.error(err)}`), + ), + Type.InvalidOutput, + Source.GetIdent, + ), + ); + } + }); + }); + } + + public validate( + origin: SessionAction, + target: string, + fields: Field[], + ): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + const options: ComponentOptions = { fields: fields, uuid: target }; + return new Promise((resolve, reject) => { + this.native + .validate( + protocol.encodeSessionAction(origin), + protocol.encodeComponentOptions(options), + ) + .then((buf: Uint8Array) => { + try { + resolve(protocol.decodeFieldsValidationErrors(buf)); + } catch (err) { + reject( + new NativeError( + new Error( + this.logger.error( + `Fail to decode message: ${utils.error(err)}`, + ), + ), + Type.InvalidOutput, + Source.ComponentsValidate, + ), + ); + } + }); + }); + } + + public abort(fields: string[]): Error | undefined { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return err; + } + try { + this.native.abort(fields); + return undefined; + } catch (err) { + return NativeError.from(err); + } + } + + protected getSessionAccessErr(): Error | undefined { + switch (this.state) { + case State.Destroying: + return new Error(`Session is already in destroy process`); + case State.Destroyed: + return new Error(`Session is already destroyed`); + case State.Created: + return new Error(`Session wasn't inited yet`); + case State.Unavailable: + return new Error(`Session is unavailable`); + case State.Available: + return undefined; + } + } + + public installedPluginsList(): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + return new Promise((resolve, reject) => { + this.native.installedPluginsList().then((buf: Uint8Array) => { + try { + resolve(protocol.decodePluginsList(buf)); + } catch (err) { + reject( + new NativeError( + new Error( + this.logger.error(`Fail to decode message: ${utils.error(err)}`), + ), + Type.InvalidOutput, + Source.Plugins, + ), + ); + } + }); + }); + } + + public invalidPluginsList(): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + + return new Promise((resolve, reject) => { + this.native.invalidPluginsList().then((buf: Uint8Array) => { + try { + resolve(protocol.decodeInvalidPluginsList(buf)); + } catch (err) { + reject( + new NativeError( + new Error( + this.logger.error(`Fail to decode message: ${utils.error(err)}`), + ), + Type.InvalidOutput, + Source.Plugins, + ), + ); + } + }); + }); + } + + public installedPluginsPaths(): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + + return new Promise((resolve, reject) => { + this.native.installedPluginsPaths().then((buf: Uint8Array) => { + try { + resolve(protocol.decodePluginsPathsList(buf)); + } catch (err) { + reject( + new NativeError( + new Error( + this.logger.error(`Fail to decode message: ${utils.error(err)}`), + ), + Type.InvalidOutput, + Source.Plugins, + ), + ); + } + }); + }); + } + + public invalidPluginsPaths(): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + + return new Promise((resolve, reject) => { + this.native.invalidPluginsPaths().then((buf: Uint8Array) => { + try { + resolve(protocol.decodePluginsPathsList(buf)); + } catch (err) { + reject( + new NativeError( + new Error( + this.logger.error(`Fail to decode message: ${utils.error(err)}`), + ), + Type.InvalidOutput, + Source.Plugins, + ), + ); + } + }); + }); + } + + public installedPluginsInfo(plugin_path: string): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + + return new Promise((resolve, reject) => { + this.native.installedPluginsInfo(plugin_path).then((buf: Uint8Array | undefined) => { + if (!buf) { + return resolve(undefined); + } + try { + resolve(protocol.decodePluginEntity(buf)); + } catch (err) { + reject( + new NativeError( + new Error( + this.logger.error(`Fail to decode message: ${utils.error(err)}`), + ), + Type.InvalidOutput, + Source.Plugins, + ), + ); + } + }); + }); + } + + public invalidPluginsInfo(plugin_path: string): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + + return new Promise((resolve, reject) => { + this.native.invalidPluginsInfo(plugin_path).then((buf: Uint8Array | undefined) => { + if (!buf) { + return resolve(undefined); + } + try { + resolve(protocol.decodeInvalidPluginEntity(buf)); + } catch (err) { + reject( + new NativeError( + new Error( + this.logger.error(`Fail to decode message: ${utils.error(err)}`), + ), + Type.InvalidOutput, + Source.Plugins, + ), + ); + } + }); + }); + } + + public getPluginRunData(plugin_path: string): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + + return new Promise((resolve, reject) => { + this.native.getPluginRunData(plugin_path).then((buf: Uint8Array | undefined) => { + if (!buf) { + return resolve(undefined); + } + try { + resolve(protocol.decodePluginRunData(buf)); + } catch (err) { + reject( + new NativeError( + new Error( + this.logger.error(`Fail to decode message: ${utils.error(err)}`), + ), + Type.InvalidOutput, + Source.Plugins, + ), + ); + } + }); + }); + } + + public reloadPlugins(): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + + return new Promise((resolve, reject) => { + this.native.reloadPlugins().catch((err: Error) => { + reject(err); + }); + }); + } + + public addPlugin(plugin_path: string): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + + return new Promise((resolve, reject) => { + this.native.addPlugin(plugin_path).catch((err: Error) => { + reject(err); + }); + }); + } + + public removePlugin(plugin_path: string): Promise { + const err = this.getSessionAccessErr(); + if (err instanceof Error) { + return Promise.reject(err); + } + + return new Promise((resolve, reject) => { + this.native.removePlugin(plugin_path).catch((err: Error) => { + reject(err); + }); + }); + } +} diff --git a/application/apps/rustcore/ts-bindings/src/native/native.jobs.ts b/application/apps/rustcore/ts-bindings/src/native/native.jobs.ts index 354e1a657..0904a6b71 100644 --- a/application/apps/rustcore/ts-bindings/src/native/native.jobs.ts +++ b/application/apps/rustcore/ts-bindings/src/native/native.jobs.ts @@ -50,19 +50,6 @@ export abstract class JobsNative { is_word: boolean; }, ): Promise; - public abstract installedPluginsList(sequence: number): Promise; - public abstract invalidPluginsList(sequence: number): Promise; - public abstract installedPluginsPaths(sequence: number): Promise; - public abstract invalidPluginsPaths(sequence: number): Promise; - public abstract installedPluginsInfo( - sequence: number, - plugin_path: string, - ): Promise; - public abstract invalidPluginsInfo(sequence: number, plugin_path: string): Promise; - public abstract getPluginRunData(sequence: number, plugin_path: string): Promise; - public abstract reloadPlugins(sequence: number): Promise; - public abstract addPlugin(sequence: number, plugin_path: string): Promise; - public abstract removePlugin(sequence: number, plugin_path: string): Promise; } interface Job { diff --git a/application/apps/rustcore/ts-bindings/src/native/native.session.ts b/application/apps/rustcore/ts-bindings/src/native/native.session.ts index eeece0c67..1425d05b1 100644 --- a/application/apps/rustcore/ts-bindings/src/native/native.session.ts +++ b/application/apps/rustcore/ts-bindings/src/native/native.session.ts @@ -3,18 +3,17 @@ import { RustSessionRequiered } from '../native/native.session.required'; import { TEventEmitter } from '../provider/provider.general'; import { Computation } from '../provider/provider'; import { IFilter } from 'platform/types/filter'; -import { GrabbedElement } from 'platform/types/bindings/miscellaneous'; +import { GrabbedElement, SourceDefinition } from 'platform/types/bindings/miscellaneous'; import { getNativeModule } from '../native/native'; import { EFileOptionsRequirements } from '../api/executors/session.stream.observe.executor'; import { Type, Source, NativeError } from '../interfaces/errors'; import { v4 as uuidv4 } from 'uuid'; import { getValidNum } from '../util/numbers'; -import { IRange, fromTuple } from 'platform/types/range'; -import { ISourceLink } from 'platform/types/observe/types'; +import { IRange } from 'platform/types/range'; import { IndexingMode, Attachment } from 'platform/types/content'; import { Logger, utils } from 'platform/log'; import { scope } from 'platform/env/scope'; -import { IObserve } from 'platform/types/observe'; +import { SessionSetup } from 'platform/types/bindings'; import { TextExportOptions } from 'platform/types/exporting'; import * as protocol from 'protocol'; @@ -22,14 +21,14 @@ import * as types from 'platform/types'; export type RustSessionConstructorImpl = new ( uuid: string, - provider: Computation, + provider: Computation, cb: (err: Error | undefined) => void, ) => T; export type TCanceler = () => void; // Create abstract class to declare available methods export abstract class RustSession extends RustSessionRequiered { - constructor(uuid: string, provider: Computation) { + constructor(uuid: string, provider: Computation) { super(); } @@ -87,7 +86,7 @@ export abstract class RustSession extends RustSessionRequiered { * Returns list of observed sources. * @returns { string } */ - public abstract getSourcesDefinitions(): Promise; + public abstract getSourcesDefinitions(): Promise; public abstract getUuid(): string; @@ -135,7 +134,7 @@ export abstract class RustSession extends RustSessionRequiered { * async operation. After TCanceler was called, @event destroy of @param emitter would be expected to * confirm cancelation. */ - public abstract observe(source: IObserve, operationUuid: string): Promise; + public abstract observe(options: SessionSetup, operationUuid: string): Promise; public abstract export( dest: string, @@ -373,7 +372,7 @@ export abstract class RustSessionNative { export function rustSessionFactory( uuid: string, - provider: Computation, + provider: Computation, ): Promise { return new Promise((resolve, reject) => { const session = new RustSessionConstructor(uuid, provider, (err: Error | undefined) => { @@ -393,11 +392,11 @@ export class RustSessionWrapper extends RustSession { private readonly _uuid: string; private readonly _native: RustSessionNative; private _assigned: boolean = false; - private _provider: Computation; + private _provider: Computation; constructor( uuid: string, - provider: Computation, + provider: Computation, cb: (err: Error | undefined) => void, ) { super(uuid, provider); @@ -463,7 +462,7 @@ export class RustSessionWrapper extends RustSession { return this._native.getUuid(); } - public getSourcesDefinitions(): Promise { + public getSourcesDefinitions(): Promise { return new Promise((resolve, reject) => { this._provider.debug().emit.operation('getSourcesDefinitions'); this._native @@ -741,12 +740,12 @@ export class RustSessionWrapper extends RustSession { return ''; } - public observe(source: IObserve, operationUuid: string): Promise { + public observe(options: SessionSetup, operationUuid: string): Promise { return new Promise((resolve, reject) => { try { this._provider.debug().emit.operation('observe', operationUuid); this._native - .observe(protocol.encodeObserveOptions(source), operationUuid) + .observe(protocol.encodeSessionSetup(options), operationUuid) .then(resolve) .catch((err: Error) => { reject(NativeError.from(err)); diff --git a/application/apps/rustcore/ts-bindings/src/native/native.ts b/application/apps/rustcore/ts-bindings/src/native/native.ts index 46a6d9167..b17f7e0f4 100644 --- a/application/apps/rustcore/ts-bindings/src/native/native.ts +++ b/application/apps/rustcore/ts-bindings/src/native/native.ts @@ -8,6 +8,7 @@ import * as fs from 'fs'; export interface IRustModuleExports { RustSession: any; UnboundJobs: any; + Components: any; RustProgressTracker: any; } diff --git a/application/apps/rustcore/ts-bindings/src/native/remarks.txt b/application/apps/rustcore/ts-bindings/src/native/remarks.txt deleted file mode 100644 index e41a33ed6..000000000 --- a/application/apps/rustcore/ts-bindings/src/native/remarks.txt +++ /dev/null @@ -1,52 +0,0 @@ -12-11-2021 -- add time measurements for JS side too and compare it with Rust scope - -e.g. show text file content - -1. create session => RustSession - - -// TODO: -- rename computation to something like Emitter or EventsStore or EventSource or EventProvider etc... -- rename channel to just session. - -// Possible events -{ Progress: { uuid: string, ticks: { ... }}}, where uuid - reference to async operation -{ Done: { uuid: string, status: finished | canceled }} - - -// -//-js-------------------------> Rust: - -let text_session = new RustSession(emitter callback); - - ----------------------------> rust-session-channel - -JS -> RUST: assign file to session -// rust will build index asynchronically - - ----------------------------> rust-session-channel.file = "path/to/textfile.txt" - ----------------------------> rust is indexing - // [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] - // [xxxxx] => report progress 100 rows from 1000 rows - //... - JS <- RUST: Event::Progress (callback(PROGRESS, (100, 1000))) - ... - JS -> RUST: grabStreamChunk(20, 10) - JS <- RUST: content [20..30] - JS -> RUST: grabStreamChunk(900, 10) - JS <- RUST: Error(content not yet available) - ... - JS <- RUST: Event::Progress (callback(PROGRESS, 1000, 1000)) - JS <- RUST: Event::Finished (callback(FINISHED)) - ...fully initialized - - JS -> RUST: grabStreamChunk(900, 10) - JS <- RUST: content [900..910] - ... - Js -> RUST: setSearch(filters) - // Rust: session-file: Path, index-of-file, search-filters: [String], index-of-search-results - RUST -> RipGrep: create search result file & index - JS <- RUST: Event::Progress (callback(SEARCH_PROGRESS, 40)) - - diff --git a/application/apps/rustcore/ts-bindings/src/provider/provider.ts b/application/apps/rustcore/ts-bindings/src/provider/provider.ts index 7afd6d5ac..483fdbf3c 100644 --- a/application/apps/rustcore/ts-bindings/src/provider/provider.ts +++ b/application/apps/rustcore/ts-bindings/src/provider/provider.ts @@ -16,7 +16,7 @@ export interface IOrderStat { emitted: number; // Time of emitting event or operation duration: number; } -export abstract class Computation { +export abstract class Computation { private _destroyed: boolean = false; protected readonly uuid: string; protected readonly tracking: { @@ -77,10 +77,6 @@ export abstract class Computation public abstract getEventsSignatures(): IEventsSignatures; - public abstract getEventsInterfaces(): IEventsInterfaces; - - public abstract getConvertor(event: string, data: T): T | O | Error; - public debug(): { getEvents(): { unsupported: Subject; @@ -285,25 +281,8 @@ export abstract class Computation } else { this.debug().emit.event(event); } - const err: Error | undefined = validateEventDesc( - (this.getEventsInterfaces() as any)[event], - data, - ); - if (err instanceof Error) { - this.debug().emit.error(`Error: ${error(err)}. Input: ${JSON.stringify(data)}`); - this.logger.error(`Failed to parse event "${event}" due error: ${error(err)}`); - } else { - const converted = data === null ? data : this.getConvertor(event, data); - if (converted instanceof Error) { - this.logger.error( - `Failed to convert results fro event "${event}" due error: ${error( - converted, - )}`, - ); - } - (this.getEvents() as any)[event].emit(converted); - this.logger.verbose(`Event "${event}" is processed`); - } + (this.getEvents() as any)[event].emit(data); + this.logger.verbose(`Event "${event}" is processed`); } }, 0); } diff --git a/application/client/src/app/register/services.ts b/application/client/src/app/register/services.ts index 5ab9a4233..16659fa8e 100644 --- a/application/client/src/app/register/services.ts +++ b/application/client/src/app/register/services.ts @@ -30,6 +30,10 @@ export const services: { [key: string]: Inputs } = { name: 'Jobs', uuid: v4(), }, + components: { + name: 'Components', + uuid: v4(), + }, files: { name: 'Files', uuid: v4(), diff --git a/application/client/src/app/schema/render/columns.ts b/application/client/src/app/schema/render/columns.ts index 5a00f9819..2ab96532d 100644 --- a/application/client/src/app/schema/render/columns.ts +++ b/application/client/src/app/schema/render/columns.ts @@ -4,9 +4,35 @@ import { Subject, Subjects } from '@platform/env/subscription'; import { error } from '@platform/log/utils'; import { bridge } from '@service/bridge'; import { LimittedValue } from '@ui/env/entities/value.limited'; +import { Render } from './index'; import * as num from '@platform/env/num'; +const MIN_COLUMN_WIDTH = 30; +const MAX_COLUMN_WIDTH = 600; + +export class ColumnsRender extends Render { + constructor(protected readonly headers: string[], sizes: number[]) { + super(); + this.setBoundEntity( + new Columns( + headers, + true, + sizes.map((w) => (w === 0 ? -1 : w)), + MIN_COLUMN_WIDTH, + MAX_COLUMN_WIDTH, + ), + ); + } + + public override columns(): number { + return this.headers.length; + } + public override delimiter(): string | undefined { + return `\u0004`; + } +} + export interface Header { caption: string; desc: string; @@ -20,10 +46,7 @@ export class Columns { protected readonly styles: Map = new Map(); protected readonly logger = scope.getLogger('Columns'); protected readonly defaults: { - headers: { - caption: string; - desc: string; - }[]; + headers: string[]; visability: boolean[] | boolean; widths: number[]; min: number[] | number; @@ -52,34 +75,26 @@ export class Columns { ) as number[]); this.headers.clear(); this.styles.clear(); - this.defaults.headers.forEach( - ( - desc: { - caption: string; - desc: string; - }, - index: number, - ) => { - const header = { - caption: desc.caption, - desc: desc.desc, - width: - this.defaults.widths[index] === -1 - ? undefined - : new LimittedValue( - `column_width_${index}`, - minWidths[index], - maxWidths[index], - this.defaults.widths[index], - ), - visible: headersVisability[index], - color: undefined, - index, - }; - this.headers.set(index, header); - this.styles.set(index, {}); - }, - ); + this.defaults.headers.forEach((caption, index: number) => { + const header = { + caption: caption, + desc: caption, + width: + this.defaults.widths[index] === -1 + ? undefined + : new LimittedValue( + `column_width_${index}`, + minWidths[index], + maxWidths[index], + this.defaults.widths[index], + ), + visible: headersVisability[index], + color: undefined, + index, + }; + this.headers.set(index, header); + this.styles.set(index, {}); + }); this.update().all(); this.hash = this.getHash(); } @@ -183,10 +198,7 @@ export class Columns { }); constructor( - headers: { - caption: string; - desc: string; - }[], + headers: string[], visability: boolean[] | boolean, widths: number[], min: number[] | number, diff --git a/application/client/src/app/schema/render/dlt.ts b/application/client/src/app/schema/render/dlt.ts deleted file mode 100644 index 5ca730c3f..000000000 --- a/application/client/src/app/schema/render/dlt.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Render } from './index'; -import { Columns } from './columns'; -import { Protocol } from '@platform/types/observe/parser/index'; - -const MIN_COLUMN_WIDTH = 30; -const MAX_COLUMN_WIDTH = 600; -// public readonly widths: number[] = [150, 80, 80, 80, 80, 80, 80, 80, 80, 80, -1]; - -export class Implementation extends Render { - public static HEADERS = [ - { - caption: 'Datetime', - desc: 'Datetime', - }, - { - caption: 'ECUID', - desc: 'ECU', - }, - { - caption: 'VERS', - desc: 'Dlt Protocol Version (VERS)', - }, - { - caption: 'SID', - desc: 'Session ID (SEID)', - }, - { - caption: 'MCNT', - desc: 'Message counter (MCNT)', - }, - { - caption: 'TMS', - desc: 'Timestamp (TMSP)', - }, - { - caption: 'EID', - desc: 'ECU', - }, - { - caption: 'APID', - desc: 'Application ID (APID)', - }, - { - caption: 'CTID', - desc: 'Context ID (CTID)', - }, - { - caption: 'MSTP', - desc: 'Message Type (MSTP)', - }, - { - caption: 'PAYLOAD', - desc: 'Payload', - }, - ]; - - constructor() { - super(); - this.setBoundEntity( - new Columns( - Implementation.HEADERS, - true, - [150, 20, 20, 20, 20, 20, 20, 20, 20, 20, -1], - MIN_COLUMN_WIDTH, - MAX_COLUMN_WIDTH, - ), - ); - } - - public override protocol(): Protocol { - return Protocol.Dlt; - } - - public override columns(): number { - return Implementation.HEADERS.length; - } - public override delimiter(): string | undefined { - return `\u0004`; - } -} diff --git a/application/client/src/app/schema/render/index.ts b/application/client/src/app/schema/render/index.ts index eb6013d96..43f28ee62 100644 --- a/application/client/src/app/schema/render/index.ts +++ b/application/client/src/app/schema/render/index.ts @@ -1,13 +1,10 @@ -import { Protocol } from '@platform/types/observe/parser/index'; - export interface RenderReference { new (): Render; } + export abstract class Render { private _bound: T | undefined; - abstract protocol(): Protocol; - public delimiter(): string | undefined { return undefined; } diff --git a/application/client/src/app/schema/render/plugin.ts b/application/client/src/app/schema/render/plugin.ts index e00e4a275..a045ff606 100644 --- a/application/client/src/app/schema/render/plugin.ts +++ b/application/client/src/app/schema/render/plugin.ts @@ -1,7 +1,6 @@ import { PluginEntity } from '@platform/types/bindings'; import { Render } from './index'; import { Columns } from './columns'; -import { Protocol } from '@platform/types/observe/parser'; export class Implementation extends Render { private _columnsCount: number = 0; @@ -35,7 +34,8 @@ export class Implementation extends Render { this.setBoundEntity( new Columns( - headers, + // headers, + [], true, widths, columns_options.min_width, @@ -45,10 +45,6 @@ export class Implementation extends Render { } } - override protocol(): Protocol { - return Protocol.Plugin; - } - public override columns(): number { return this._columnsCount; } diff --git a/application/client/src/app/schema/render/someip.ts b/application/client/src/app/schema/render/someip.ts deleted file mode 100644 index 9fe995d46..000000000 --- a/application/client/src/app/schema/render/someip.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Render } from './index'; -import { Columns } from './columns'; -import { Protocol } from '@platform/types/observe/parser/index'; - -const MIN_COLUMN_WIDTH = 30; -const MAX_COLUMN_WIDTH = 600; - -export class Implementation extends Render { - public static HEADERS = [ - { - caption: 'SOME/IP', - desc: 'The Message-Kind.', - }, - { - caption: 'SERV', - desc: 'The Service-ID', - }, - { - caption: 'METH', - desc: 'The Method-ID', - }, - { - caption: 'LENG', - desc: 'The Length-Field', - }, - { - caption: 'CLID', - desc: 'The Client-ID', - }, - { - caption: 'SEID', - desc: 'The Session-ID', - }, - { - caption: 'IVER', - desc: 'The Interface-Version', - }, - { - caption: 'MSTP', - desc: 'The Message-Type', - }, - { - caption: 'RETC', - desc: 'The Return-Code', - }, - { - caption: 'PAYLOAD', - desc: 'Payload', - }, - ]; - - constructor() { - super(); - this.setBoundEntity( - new Columns( - Implementation.HEADERS, - true, - [50, 50, 50, 30, 30, 30, 30, 30, 30, -1], - MIN_COLUMN_WIDTH, - MAX_COLUMN_WIDTH, - ), - ); - } - - public override protocol(): Protocol { - return Protocol.SomeIp; - } - - public override columns(): number { - return Implementation.HEADERS.length; - } - public override delimiter(): string | undefined { - return `\u0004`; - } -} diff --git a/application/client/src/app/schema/render/text.ts b/application/client/src/app/schema/render/text.ts index 59e8ba6e3..b24cc56e4 100644 --- a/application/client/src/app/schema/render/text.ts +++ b/application/client/src/app/schema/render/text.ts @@ -1,8 +1,3 @@ import { Render } from './index'; -import { Protocol } from '@platform/types/observe/parser/index'; -export class Implementation extends Render { - public override protocol(): Protocol { - return Protocol.Text; - } -} +export class TextRender extends Render {} diff --git a/application/client/src/app/schema/render/tools.ts b/application/client/src/app/schema/render/tools.ts deleted file mode 100644 index c7df37745..000000000 --- a/application/client/src/app/schema/render/tools.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Implementation as Dlt } from './dlt'; -import { Implementation as SomeIp } from './someip'; -import { Implementation as Text } from './text'; -import { Implementation as Plugin } from './plugin'; -import { Render, RenderReference } from './index'; -import { Session } from '@service/session/session'; -import { Observe } from '@platform/types/observe'; -import { plugins } from '@service/plugins'; - -import * as Parsers from '@platform/types/observe/parser/index'; - -const RENDERS: { - [key: string]: RenderReference; -} = { - [Parsers.Protocol.Dlt]: Dlt, - [Parsers.Protocol.SomeIp]: SomeIp, - [Parsers.Protocol.Text]: Text, -}; - -export async function getRender(observe: Observe): Promise | Error> { - const protocol = observe.parser.instance.alias(); - // Render options on plugins can't be static because they must be retrieved - // from the plugin itself. - if (protocol === Parsers.Protocol.Plugin) { - const config = observe.parser.as( - Parsers.Plugin.Configuration, - ); - - if (config === undefined) { - return new Error('No parser configurations for plugin.'); - } - - const pluginPath = config.configuration.plugin_path; - const parser = plugins - .list() - .preload() - .find((p) => p.info.wasm_file_path === pluginPath); - - if (parser === undefined) { - return new Error("Selected parser plugin does'n exit"); - } - - return new Plugin(parser); - } - - const Ref = RENDERS[protocol]; - return Ref === undefined ? new Error(`No render has been found for "${protocol}"`) : new Ref(); -} - -export function getLinkedProtocol(smth: Session | Render): Parsers.Protocol | Error { - return smth instanceof Session ? smth.render.protocol() : smth.protocol(); -} - -export function isRenderMatch(session: Session, render: Render): boolean | Error { - const assigned = getLinkedProtocol(session); - return assigned === render.protocol(); -} diff --git a/application/client/src/app/service/actions.ts b/application/client/src/app/service/actions.ts index 3fc33786c..24127cd72 100644 --- a/application/client/src/app/service/actions.ts +++ b/application/client/src/app/service/actions.ts @@ -2,9 +2,7 @@ import { SetupService, Interface, Implementation, register } from '@platform/ent import { services } from '@register/services'; import { api } from '@service/api'; import { CancelablePromise } from '@platform/env/promise'; -import { FileType } from '@platform/types/observe/types/file'; -import { Protocol } from '@platform/types/observe/parser'; -import { Source } from '@platform/types/observe/origin/stream/index'; +import { FileType } from '@platform/types/files'; import * as Requests from '@platform/ipc/request'; import * as handlers from '@service/actions/index'; @@ -105,59 +103,60 @@ export class Service extends Implementation { ): CancelablePromise => { return new CancelablePromise((resolve, _reject) => { (() => { - switch (request.protocol) { - case Protocol.Text: - switch (request.source) { - case undefined: - case Source.Process: - return new handlers.StdoutText.Action().apply(); - case Source.Serial: - return new handlers.SerialText.Action().apply(); - default: - return Promise.reject( - new Error( - `Unsupported transport for Text: ${request.source}`, - ), - ); - } - case Protocol.Dlt: - switch (request.source) { - case undefined: - case Source.UDP: - return new handlers.UdpDlt.Action().apply(); - case Source.TCP: - return new handlers.TcpDlt.Action().apply(); - default: - return Promise.reject( - new Error( - `Unsupported transport for DLT: ${request.source}`, - ), - ); - } - case Protocol.Plugin: { - switch (request.source) { - case undefined: - case Source.Process: - return new handlers.StdoutPlugin.Action().apply(); - case Source.Serial: - return new handlers.SerialParserPlugin.Action().apply(); - case Source.UDP: - return new handlers.UdpParserPlugin.Action().apply(); - case Source.TCP: - return new handlers.TcpParserPlugin.Action().apply(); - default: - return Promise.reject( - new Error( - `Unsupported transport for Plugins: ${request.source}`, - ), - ); - } - } - default: - return Promise.reject( - new Error(`Unsupported format: ${request.protocol}`), - ); - } + return Promise.reject(new Error(`Not implemented`)); + // switch (request.protocol) { + // case Protocol.Text: + // switch (request.source) { + // case undefined: + // case Source.Process: + // return new handlers.StdoutText.Action().apply(); + // case Source.Serial: + // return new handlers.SerialText.Action().apply(); + // default: + // return Promise.reject( + // new Error( + // `Unsupported transport for Text: ${request.source}`, + // ), + // ); + // } + // case Protocol.Dlt: + // switch (request.source) { + // case undefined: + // case Source.UDP: + // return new handlers.UdpDlt.Action().apply(); + // case Source.TCP: + // return new handlers.TcpDlt.Action().apply(); + // default: + // return Promise.reject( + // new Error( + // `Unsupported transport for DLT: ${request.source}`, + // ), + // ); + // } + // case Protocol.Plugin: { + // switch (request.source) { + // case undefined: + // case Source.Process: + // return new handlers.StdoutPlugin.Action().apply(); + // case Source.Serial: + // return new handlers.SerialParserPlugin.Action().apply(); + // case Source.UDP: + // return new handlers.UdpParserPlugin.Action().apply(); + // case Source.TCP: + // return new handlers.TcpParserPlugin.Action().apply(); + // default: + // return Promise.reject( + // new Error( + // `Unsupported transport for Plugins: ${request.source}`, + // ), + // ); + // } + // } + // default: + // return Promise.reject( + // new Error(`Unsupported format: ${request.protocol}`), + // ); + // } })() .then(() => resolve( diff --git a/application/client/src/app/service/actions/file.any.ts b/application/client/src/app/service/actions/file.any.ts index 73d8dee9d..dc49743fd 100644 --- a/application/client/src/app/service/actions/file.any.ts +++ b/application/client/src/app/service/actions/file.any.ts @@ -3,9 +3,9 @@ import { bridge } from '@service/bridge'; import { session } from '@service/session'; import { TabSourceMultipleFiles } from '@ui/tabs/multiplefiles/component'; import { File } from '@platform/types/files'; -import { FileType } from '@platform/types/observe/types/file'; +import { FileType } from '@platform/types/files'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'open_any_file'; @@ -39,33 +39,13 @@ export class Action extends Base { case FileType.Binary: case FileType.PcapNG: case FileType.PcapLegacy: - session - .initialize() - .configure( - new Factory.File().type(file.type).file(file.filename).asDlt().get(), - ); + session.initialize().configure(SessionOrigin.file(file.filename)); break; case FileType.Text: - session - .initialize() - .observe( - new Factory.File() - .type(Factory.FileType.Text) - .file(file.filename) - .asText() - .get(), - ); + session.initialize().observe(SessionOrigin.file(file.filename)); break; case FileType.ParserPlugin: - session - .initialize() - .observe( - new Factory.File() - .type(Factory.FileType.ParserPlugin) - .file(file.filename) - .asParserPlugin() - .get(), - ); + session.initialize().observe(SessionOrigin.file(file.filename)); break; } } diff --git a/application/client/src/app/service/actions/file.dlt.ts b/application/client/src/app/service/actions/file.dlt.ts index fcea279e4..9e6d2c58c 100644 --- a/application/client/src/app/service/actions/file.dlt.ts +++ b/application/client/src/app/service/actions/file.dlt.ts @@ -3,7 +3,7 @@ import { bridge } from '@service/bridge'; import { session } from '@service/session'; import { TabSourceMultipleFiles } from '@ui/tabs/multiplefiles/component'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'open_dlt_file'; @@ -35,11 +35,7 @@ export class Action extends Base { }, }); } else { - session - .initialize() - .configure( - new Factory.File().type(files[0].type).file(files[0].filename).asDlt().get(), - ); + session.initialize().configure(SessionOrigin.file(files[0].filename)); } return Promise.resolve(); } diff --git a/application/client/src/app/service/actions/file.parserPlugin.ts b/application/client/src/app/service/actions/file.parserPlugin.ts index 527ce7af8..a3be8f018 100644 --- a/application/client/src/app/service/actions/file.parserPlugin.ts +++ b/application/client/src/app/service/actions/file.parserPlugin.ts @@ -3,7 +3,7 @@ import { bridge } from '@service/bridge'; import { session } from '@service/session'; import { TabSourceMultipleFiles } from '@ui/tabs/multiplefiles/component'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'open_file_parser_plugins'; @@ -36,15 +36,7 @@ export class Action extends Base { }, }); } else { - session - .initialize() - .configure( - new Factory.File() - .type(files[0].type) - .file(files[0].filename) - .asParserPlugin() - .get(), - ); + session.initialize().configure(SessionOrigin.file(files[0].filename)); } return Promise.resolve(); } diff --git a/application/client/src/app/service/actions/file.pcap.ts b/application/client/src/app/service/actions/file.pcap.ts index 6cc89148d..46daeeac1 100644 --- a/application/client/src/app/service/actions/file.pcap.ts +++ b/application/client/src/app/service/actions/file.pcap.ts @@ -3,7 +3,7 @@ import { bridge } from '@service/bridge'; import { session } from '@service/session'; import { TabSourceMultipleFiles } from '@ui/tabs/multiplefiles/component'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'open_pcap_legacy_file'; @@ -35,9 +35,7 @@ export class Action extends Base { }, }); } else { - session - .initialize() - .configure(new Factory.File().type(files[0].type).file(files[0].filename).get()); + session.initialize().configure(SessionOrigin.file(files[0].filename)); } return Promise.resolve(); } diff --git a/application/client/src/app/service/actions/file.pcapng.ts b/application/client/src/app/service/actions/file.pcapng.ts index d98dfa224..af0ab3505 100644 --- a/application/client/src/app/service/actions/file.pcapng.ts +++ b/application/client/src/app/service/actions/file.pcapng.ts @@ -3,7 +3,7 @@ import { bridge } from '@service/bridge'; import { session } from '@service/session'; import { TabSourceMultipleFiles } from '@ui/tabs/multiplefiles/component'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'open_pcap_file'; @@ -35,9 +35,7 @@ export class Action extends Base { }, }); } else { - session - .initialize() - .configure(new Factory.File().type(files[0].type).file(files[0].filename).get()); + session.initialize().configure(SessionOrigin.file(files[0].filename)); } return Promise.resolve(); } diff --git a/application/client/src/app/service/actions/file.plugins.ts b/application/client/src/app/service/actions/file.plugins.ts index 527ce7af8..c6a1e524a 100644 --- a/application/client/src/app/service/actions/file.plugins.ts +++ b/application/client/src/app/service/actions/file.plugins.ts @@ -1,10 +1,9 @@ import { Base } from './action'; import { bridge } from '@service/bridge'; import { session } from '@service/session'; +import { SessionOrigin } from '@service/session/origin'; import { TabSourceMultipleFiles } from '@ui/tabs/multiplefiles/component'; -import * as Factory from '@platform/types/observe/factory'; - export const ACTION_UUID = 'open_file_parser_plugins'; export class Action extends Base { @@ -36,15 +35,7 @@ export class Action extends Base { }, }); } else { - session - .initialize() - .configure( - new Factory.File() - .type(files[0].type) - .file(files[0].filename) - .asParserPlugin() - .get(), - ); + session.initialize().configure(SessionOrigin.file(files[0].filename)); } return Promise.resolve(); } diff --git a/application/client/src/app/service/actions/file.text.ts b/application/client/src/app/service/actions/file.text.ts index 5517ab5dc..abc84dcd8 100644 --- a/application/client/src/app/service/actions/file.text.ts +++ b/application/client/src/app/service/actions/file.text.ts @@ -3,7 +3,7 @@ import { bridge } from '@service/bridge'; import { session } from '@service/session'; import { TabSourceMultipleFiles } from '@ui/tabs/multiplefiles/component'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'open_text_file'; @@ -35,7 +35,7 @@ export class Action extends Base { }, }); } else { - session.initialize().observe(new Factory.File().type(files[0].type).file(files[0].filename).asText().get()); + session.initialize().observe(SessionOrigin.file(files[0].filename)); } return Promise.resolve(); } diff --git a/application/client/src/app/service/actions/folder.any.ts b/application/client/src/app/service/actions/folder.any.ts index 9b2cf7cb1..67d83a009 100644 --- a/application/client/src/app/service/actions/folder.any.ts +++ b/application/client/src/app/service/actions/folder.any.ts @@ -2,10 +2,10 @@ import { Base } from './action'; import { bridge } from '@service/bridge'; import { session } from '@service/session'; import { TabSourceMultipleFiles } from '@ui/tabs/multiplefiles/component'; -import { FileType } from '@platform/types/observe/types/file'; +import { FileType } from '@platform/types/files'; import { notifications, Notification } from '@ui/service/notifications'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'open_any_folder'; @@ -47,37 +47,13 @@ export class Action extends Base { case FileType.Binary: case FileType.PcapNG: case FileType.PcapLegacy: - session - .initialize() - .configure( - new Factory.File() - .type(files[0].type) - .file(files[0].filename) - .asDlt() - .get(), - ); + session.initialize().configure(SessionOrigin.file(files[0].filename)); break; case FileType.Text: - session - .initialize() - .observe( - new Factory.File() - .type(files[0].type) - .file(files[0].filename) - .asText() - .get(), - ); + session.initialize().observe(SessionOrigin.file(files[0].filename)); break; case FileType.ParserPlugin: - session - .initialize() - .observe( - new Factory.File() - .type(files[0].type) - .file(files[0].filename) - .asParserPlugin() - .get(), - ); + session.initialize().observe(SessionOrigin.file(files[0].filename)); break; } } diff --git a/application/client/src/app/service/actions/folder.dlt.ts b/application/client/src/app/service/actions/folder.dlt.ts index 313d34cd8..04235bf8d 100644 --- a/application/client/src/app/service/actions/folder.dlt.ts +++ b/application/client/src/app/service/actions/folder.dlt.ts @@ -4,7 +4,7 @@ import { session } from '@service/session'; import { TabSourceMultipleFiles } from '@ui/tabs/multiplefiles/component'; import { notifications, Notification } from '@ui/service/notifications'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'open_dlt_folder'; @@ -43,11 +43,7 @@ export class Action extends Base { }); return Promise.resolve(); } else { - session - .initialize() - .configure( - new Factory.File().type(files[0].type).file(files[0].filename).asDlt().get(), - ); + session.initialize().configure(SessionOrigin.file(files[0].filename)); } return Promise.resolve(); } diff --git a/application/client/src/app/service/actions/folder.parserPlugin.ts b/application/client/src/app/service/actions/folder.parserPlugin.ts index afa2fbaed..fa6b58f1e 100644 --- a/application/client/src/app/service/actions/folder.parserPlugin.ts +++ b/application/client/src/app/service/actions/folder.parserPlugin.ts @@ -4,7 +4,7 @@ import { session } from '@service/session'; import { notifications, Notification } from '@ui/service/notifications'; import { TabSourceMultipleFiles } from '@ui/tabs/multiplefiles/component'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'open_folder_parser_plugins'; @@ -44,15 +44,7 @@ export class Action extends Base { }, }); } else { - session - .initialize() - .observe( - new Factory.File() - .type(files[0].type) - .file(files[0].filename) - .asParserPlugin() - .get(), - ); + session.initialize().observe(SessionOrigin.file(files[0].filename)); } return Promise.resolve(); diff --git a/application/client/src/app/service/actions/folder.pcap.ts b/application/client/src/app/service/actions/folder.pcap.ts index 2d909ffd1..744c6d509 100644 --- a/application/client/src/app/service/actions/folder.pcap.ts +++ b/application/client/src/app/service/actions/folder.pcap.ts @@ -4,7 +4,7 @@ import { session } from '@service/session'; import { TabSourceMultipleFiles } from '@ui/tabs/multiplefiles/component'; import { notifications, Notification } from '@ui/service/notifications'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'open_pcap_legacy_folder'; @@ -42,9 +42,7 @@ export class Action extends Base { }, }); } else { - session - .initialize() - .configure(new Factory.File().type(files[0].type).file(files[0].filename).get()); + session.initialize().configure(SessionOrigin.file(files[0].filename)); } return Promise.resolve(); } diff --git a/application/client/src/app/service/actions/folder.pcapng.ts b/application/client/src/app/service/actions/folder.pcapng.ts index ce8ed13b8..a28d16e4b 100644 --- a/application/client/src/app/service/actions/folder.pcapng.ts +++ b/application/client/src/app/service/actions/folder.pcapng.ts @@ -4,7 +4,7 @@ import { session } from '@service/session'; import { TabSourceMultipleFiles } from '@ui/tabs/multiplefiles/component'; import { notifications, Notification } from '@ui/service/notifications'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'open_pcap_folder'; @@ -42,9 +42,7 @@ export class Action extends Base { }, }); } else { - session - .initialize() - .configure(new Factory.File().type(files[0].type).file(files[0].filename).get()); + session.initialize().configure(SessionOrigin.file(files[0].filename)); } return Promise.resolve(); } diff --git a/application/client/src/app/service/actions/folder.plugins.ts b/application/client/src/app/service/actions/folder.plugins.ts index afa2fbaed..fa6b58f1e 100644 --- a/application/client/src/app/service/actions/folder.plugins.ts +++ b/application/client/src/app/service/actions/folder.plugins.ts @@ -4,7 +4,7 @@ import { session } from '@service/session'; import { notifications, Notification } from '@ui/service/notifications'; import { TabSourceMultipleFiles } from '@ui/tabs/multiplefiles/component'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'open_folder_parser_plugins'; @@ -44,15 +44,7 @@ export class Action extends Base { }, }); } else { - session - .initialize() - .observe( - new Factory.File() - .type(files[0].type) - .file(files[0].filename) - .asParserPlugin() - .get(), - ); + session.initialize().observe(SessionOrigin.file(files[0].filename)); } return Promise.resolve(); diff --git a/application/client/src/app/service/actions/folder.text.ts b/application/client/src/app/service/actions/folder.text.ts index c7b2b1245..958f298eb 100644 --- a/application/client/src/app/service/actions/folder.text.ts +++ b/application/client/src/app/service/actions/folder.text.ts @@ -4,7 +4,7 @@ import { session } from '@service/session'; import { TabSourceMultipleFiles } from '@ui/tabs/multiplefiles/component'; import { notifications, Notification } from '@ui/service/notifications'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'open_text_folder'; @@ -42,11 +42,7 @@ export class Action extends Base { }, }); } else { - session - .initialize() - .observe( - new Factory.File().type(files[0].type).file(files[0].filename).asText().get(), - ); + session.initialize().observe(SessionOrigin.file(files[0].filename)); } return Promise.resolve(); } diff --git a/application/client/src/app/service/actions/jumpto.ts b/application/client/src/app/service/actions/jumpto.ts index 7c3835be9..d76f45a76 100644 --- a/application/client/src/app/service/actions/jumpto.ts +++ b/application/client/src/app/service/actions/jumpto.ts @@ -31,7 +31,7 @@ export class Action extends Base { horizontal: Horizontal.center, }, closeOnKey: 'Escape', - width: 350, + size: { width: 350 }, uuid: 'Ctrl + G', blur: false, }); diff --git a/application/client/src/app/service/actions/serial.parserPlugin.ts b/application/client/src/app/service/actions/serial.parserPlugin.ts index 2035e76b6..30cab32c7 100644 --- a/application/client/src/app/service/actions/serial.parserPlugin.ts +++ b/application/client/src/app/service/actions/serial.parserPlugin.ts @@ -1,7 +1,7 @@ import { Base } from './action'; import { session } from '@service/session'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'stream_parser_plugin_on_serial'; @@ -18,7 +18,7 @@ export class Action extends Base { } public async apply(): Promise { - session.initialize().configure(new Factory.Stream().asParserPlugin().serial().get()); + session.initialize().configure(SessionOrigin.source()); return Promise.resolve(); } } diff --git a/application/client/src/app/service/actions/serial.text.ts b/application/client/src/app/service/actions/serial.text.ts index a956150c1..2e4c9cab4 100644 --- a/application/client/src/app/service/actions/serial.text.ts +++ b/application/client/src/app/service/actions/serial.text.ts @@ -1,7 +1,7 @@ import { Base } from './action'; import { session } from '@service/session'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'stream_text_on_serial'; @@ -18,7 +18,7 @@ export class Action extends Base { } public async apply(): Promise { - session.initialize().configure(new Factory.Stream().asText().serial().get()); + session.initialize().configure(SessionOrigin.source()); return Promise.resolve(); } } diff --git a/application/client/src/app/service/actions/stdout.plugin.ts b/application/client/src/app/service/actions/stdout.plugin.ts index 4b4367991..5b0ae5bf5 100644 --- a/application/client/src/app/service/actions/stdout.plugin.ts +++ b/application/client/src/app/service/actions/stdout.plugin.ts @@ -1,7 +1,7 @@ import { Base } from './action'; import { session } from '@service/session'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'stream_text_on_plugin'; @@ -17,7 +17,7 @@ export class Action extends Base { return 'Execute command with plugins'; } public override apply(): Promise { - session.initialize().configure(new Factory.Stream().process().asParserPlugin().get()); + session.initialize().configure(SessionOrigin.source()); return Promise.resolve(); } } diff --git a/application/client/src/app/service/actions/stdout.text.ts b/application/client/src/app/service/actions/stdout.text.ts index b1e31c4c0..1dc146998 100644 --- a/application/client/src/app/service/actions/stdout.text.ts +++ b/application/client/src/app/service/actions/stdout.text.ts @@ -1,7 +1,7 @@ import { Base } from './action'; import { session } from '@service/session'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'stream_text_on_stdout'; @@ -18,7 +18,7 @@ export class Action extends Base { } public async apply(): Promise { - session.initialize().configure(new Factory.Stream().asText().process().get()); + session.initialize().configure(SessionOrigin.source()); return Promise.resolve(); } } diff --git a/application/client/src/app/service/actions/tcp.dlt.ts b/application/client/src/app/service/actions/tcp.dlt.ts index 24ff06a94..ae40536ec 100644 --- a/application/client/src/app/service/actions/tcp.dlt.ts +++ b/application/client/src/app/service/actions/tcp.dlt.ts @@ -1,7 +1,7 @@ import { Base } from './action'; import { session } from '@service/session'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'stream_dlt_on_tcp'; @@ -18,7 +18,7 @@ export class Action extends Base { } public async apply(): Promise { - session.initialize().configure(new Factory.Stream().tcp().asDlt().get()); + session.initialize().configure(SessionOrigin.source()); return Promise.resolve(); } } diff --git a/application/client/src/app/service/actions/tcp.parserPlugin.ts b/application/client/src/app/service/actions/tcp.parserPlugin.ts index b46f13412..57264b17c 100644 --- a/application/client/src/app/service/actions/tcp.parserPlugin.ts +++ b/application/client/src/app/service/actions/tcp.parserPlugin.ts @@ -1,7 +1,7 @@ import { Base } from './action'; import { session } from '@service/session'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'stream_parser_plugin_on_tcp'; @@ -18,7 +18,7 @@ export class Action extends Base { } public async apply(): Promise { - session.initialize().configure(new Factory.Stream().tcp().asParserPlugin().get()); + session.initialize().configure(SessionOrigin.source()); return Promise.resolve(); } } diff --git a/application/client/src/app/service/actions/udp.dlt.ts b/application/client/src/app/service/actions/udp.dlt.ts index a62a086c3..fbd7feacf 100644 --- a/application/client/src/app/service/actions/udp.dlt.ts +++ b/application/client/src/app/service/actions/udp.dlt.ts @@ -1,7 +1,7 @@ import { Base } from './action'; import { session } from '@service/session'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'stream_dlt_on_udp'; @@ -18,7 +18,7 @@ export class Action extends Base { } public async apply(): Promise { - session.initialize().configure(new Factory.Stream().udp().asDlt().get()); + session.initialize().configure(SessionOrigin.source()); return Promise.resolve(); } } diff --git a/application/client/src/app/service/actions/udp.parserPlugin.ts b/application/client/src/app/service/actions/udp.parserPlugin.ts index 44f0faba6..e57d3e306 100644 --- a/application/client/src/app/service/actions/udp.parserPlugin.ts +++ b/application/client/src/app/service/actions/udp.parserPlugin.ts @@ -1,7 +1,7 @@ import { Base } from './action'; import { session } from '@service/session'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export const ACTION_UUID = 'stream_parser_plugin_on_udp'; @@ -18,7 +18,7 @@ export class Action extends Base { } public async apply(): Promise { - session.initialize().configure(new Factory.Stream().udp().asParserPlugin().get()); + session.initialize().configure(SessionOrigin.source()); return Promise.resolve(); } } diff --git a/application/client/src/app/service/bridge.ts b/application/client/src/app/service/bridge.ts index 30e47c449..37fece16b 100644 --- a/application/client/src/app/service/bridge.ts +++ b/application/client/src/app/service/bridge.ts @@ -2,8 +2,8 @@ import { SetupService, Interface, Implementation, register } from '@platform/ent import { services } from '@register/services'; import { File, Entity, ParsedPath } from '@platform/types/files'; import { FolderEntity } from '@platform/types/bindings'; -import { FileType } from '@platform/types/observe/types/file'; -import { DltStatisticInfo, Profile } from '@platform/types/bindings'; +import { FileType } from '@platform/types/files'; +import { Profile } from '@platform/types/bindings'; import { Entry } from '@platform/types/storage/entry'; import { error } from '@platform/log/utils'; @@ -423,27 +423,6 @@ export class Service extends Implementation { }; } - public dlt(): { - stat(files: string[]): Promise; - } { - return { - stat: (files: string[]): Promise => { - return new Promise((resolve, reject) => { - Requests.IpcRequest.send( - Requests.Dlt.Stat.Response, - new Requests.Dlt.Stat.Request({ - files, - }), - ) - .then((response) => { - resolve(response.stat); - }) - .catch(reject); - }); - }, - }; - } - public storage(key: string): { write(content: string): Promise; read(): Promise; diff --git a/application/client/src/app/service/cli/observe.ts b/application/client/src/app/service/cli/observe.ts index 4b5802873..6a16730c3 100644 --- a/application/client/src/app/service/cli/observe.ts +++ b/application/client/src/app/service/cli/observe.ts @@ -1,7 +1,6 @@ import { CancelablePromise } from '@platform/env/promise'; import { Service } from '@service/cli'; import { session } from '@service/session'; -import { Observe } from '@platform/types/observe'; import * as Requests from '@platform/ipc/request'; @@ -30,34 +29,36 @@ export async function action( } let uuid: string | undefined = undefined; for (const observe of request.observe) { - if (uuid === undefined) { - uuid = await session - .initialize() - .auto(new Observe(observe).locker().guess()) - .catch((err: Error) => { - cli.log().warn( - `Fail to apply action (Events.Cli.Observe.Event): ${err.message}`, - ); - return undefined; - }); - if (uuid === undefined) { - return Promise.resolve(undefined); - } - } else { - const instance = session.get(uuid); - if (instance === undefined) { - return Promise.resolve(undefined); - } - await session - .initialize() - .auto(new Observe(observe).locker().guess(), instance) - .catch((err: Error) => { - cli.log().warn( - `Fail to apply action (Events.Cli.Observe.Event): ${err.message}`, - ); - return undefined; - }); - } + console.error(`Not implemented`); + return Promise.reject(new Error(`Not implemented`)); + // if (uuid === undefined) { + // uuid = await session + // .initialize() + // .auto(new Observe(observe).locker().guess()) + // .catch((err: Error) => { + // cli.log().warn( + // `Fail to apply action (Events.Cli.Observe.Event): ${err.message}`, + // ); + // return undefined; + // }); + // if (uuid === undefined) { + // return Promise.resolve(undefined); + // } + // } else { + // const instance = session.get(uuid); + // if (instance === undefined) { + // return Promise.resolve(undefined); + // } + // await session + // .initialize() + // .auto(new Observe(observe).locker().guess(), instance) + // .catch((err: Error) => { + // cli.log().warn( + // `Fail to apply action (Events.Cli.Observe.Event): ${err.message}`, + // ); + // return undefined; + // }); + // } } return Promise.resolve(uuid); } diff --git a/application/client/src/app/service/components.ts b/application/client/src/app/service/components.ts new file mode 100644 index 000000000..0ab49440a --- /dev/null +++ b/application/client/src/app/service/components.ts @@ -0,0 +1,442 @@ +import { SetupService, Interface, Implementation, register } from '@platform/entity/service'; +import { services } from '@register/services'; +import { Subjects, Subject } from '@platform/env/subscription'; +import { + LoadingDoneEvent, + LoadingCancelledEvent, + LoadingErrorEvent, + LoadingErrorsEvent, +} from '@platform/types/components'; + +import * as Events from '@platform/ipc/event/index'; +import * as Requests from '@platform/ipc/request/index'; + +import { + ComponentsList, + Field, + FieldDesc, + FieldList, + Ident, + IODataType, + OutputRender, + SessionAction, +} from '@platform/types/bindings'; + +@SetupService(services['components']) +export class Service extends Implementation { + /** + * A collection of subjects used for broadcasting events related to component settings loading and validation. + * These subjects are used to notify the client about the progress and results of loading static and lazy settings. + */ + public subjects: Subjects<{ + /** + * Emitted when lazy settings have finished loading. + * Contains the fully resolved settings as if they were static. + */ + LoadingDone: Subject; + + /** + * Emitted when an error occurs while retrieving the component's configuration schema. + * Contains identifiers of the fields for which schema retrieval failed. + */ + LoadingErrors: Subject; + + /** + * Emitted in response to a validation request. + * Contains field-level validation errors for the submitted configuration data. + */ + LoadingError: Subject; + + /** + * Emitted when lazy loading of settings has been cancelled. + * Typically triggered when the user aborts the session before completion. + */ + LoadingCancelled: Subject; + }> = new Subjects({ + LoadingDone: new Subject(), + LoadingErrors: new Subject(), + LoadingError: new Subject(), + LoadingCancelled: new Subject(), + }); + + public override ready(): Promise { + this.register( + Events.IpcEvent.subscribe( + Events.Components.LoadingDone.Event, + (event) => { + this.subjects.get().LoadingDone.emit(event.event); + }, + ), + Events.IpcEvent.subscribe( + Events.Components.LoadingErrors.Event, + (event) => { + this.subjects.get().LoadingErrors.emit(event.event); + }, + ), + Events.IpcEvent.subscribe( + Events.Components.LoadingError.Event, + (event) => { + this.subjects.get().LoadingError.emit(event.event); + }, + ), + Events.IpcEvent.subscribe( + Events.Components.LoadingCancelled.Event, + (event) => { + this.subjects.get().LoadingCancelled.emit(event.event); + }, + ), + ); + return Promise.resolve(); + } + + public override destroy(): Promise { + this.unsubscribe(); + return Promise.resolve(); + } + + /** + * Aborts the lazy loading process for the specified configuration fields. + * + * The client receives the identifiers of fields marked for lazy loading + * along with the `getOptions` call. This method can be used to cancel + * loading for any of those fields, for example, if the user closes the session + * or navigates away before the data is fully loaded. + * + * @param fields - An array of field identifiers for which lazy loading should be cancelled. + * @returns A promise that resolves when the cancellation has been processed. + */ + public abort(fields: string[]): Promise { + return new Promise((resolve, reject) => { + Requests.IpcRequest.send( + Requests.Components.Abort.Response, + new Requests.Components.Abort.Request({ + fields, + }), + ) + .then((_) => { + resolve(); + }) + .catch(reject); + }); + } + + /** + * Retrieves the configuration schema (field descriptions) for the specified target components, + * such as a parser and a data source. + * + * Note that the returned schema may vary depending on the `SessionAction` context. + * For example, the DLT parser will include a lazy setting for "DLT Statistics" when the session + * is based on a DLT file. However, the same parser will not include this setting if the session + * refers to a stream instead of a file. + * + * @param origin - The session context (`SessionAction`) indicating the data source type (e.g., file or stream). + * @param targets - An array of component identifiers (usually parser and source) whose configuration schemas should be retrieved. + * @returns A promise that resolves to a map of component IDs to arrays of `FieldDesc` describing the configuration fields. + */ + public getOptions(origin: SessionAction, targets: string[]): Promise> { + return new Promise((resolve, reject) => { + Requests.IpcRequest.send( + Requests.Components.GetOptions.Response, + new Requests.Components.GetOptions.Request({ + origin, + targets, + }), + ) + .then((response) => { + resolve(response.options); + }) + .catch(reject); + }); + } + + /** + * Retrieves the output render definition for displaying parsed data. + * + * The provided `uuid` is expected to refer to a parser, since the parser + * is responsible for defining how the data should be visually represented. + * + * @param uuid - The unique identifier of the parser component. + * @returns A promise that resolves to the `OutputRender` definition used for rendering output, + * or `null`/`undefined` if no render is available. + */ + public getOutputRender(uuid: string): Promise { + return new Promise((resolve, reject) => { + Requests.IpcRequest.send( + Requests.Components.GetOutputRender.Response, + new Requests.Components.GetOutputRender.Request({ + uuid, + }), + ) + .then((response) => { + resolve(response.render); + }) + .catch(reject); + }); + } + + /** + * Checks whether the specified source supports the Source Data Exchange (SDE) mechanism. + * + * The SDE mechanism allows sending data *to* a source. This method verifies + * if the given source component (by UUID) permits such operations + * in the context of the provided session. + * + * @param uuid - The unique identifier of the source component. + * @param origin - The session context used to evaluate SDE permissions. + * @returns A promise that resolves to `true` if SDE is supported by the source, or `false` otherwise. + */ + public isSdeSupported(uuid: string, origin: SessionAction): Promise { + return new Promise((resolve, reject) => { + Requests.IpcRequest.send( + Requests.Components.IsSdeSupported.Response, + new Requests.Components.IsSdeSupported.Request({ + uuid, + origin, + }), + ) + .then((response) => { + resolve(response.support); + }) + .catch(reject); + }); + } + + /** + * Finds all components (sources and parsers) that are compatible with the given session + * and can be used without requiring manual configuration. + * + * This method is typically used in "quick start" workflows, such as when a user opens a file + * and the system needs to automatically determine which components can handle it. + * Only components that support default options and match the detected I/O type (e.g. text or binary) + * are included in the result. + * + * @param origin - The session context used to infer compatibility and I/O type. + * @returns A promise that resolves to a list of compatible component identifiers. + */ + public getCompatibleSetup(origin: SessionAction): Promise { + return new Promise((resolve, reject) => { + Requests.IpcRequest.send( + Requests.Components.GetCompatibleSetup.Response, + new Requests.Components.GetCompatibleSetup.Request({ + origin, + }), + ) + .then((response) => { + resolve(response.components); + }) + .catch(reject); + }); + } + + /** + * Retrieves the default configuration options for the specified component. + * + * If the component supports usage without explicit configuration, this method returns a list + * of default fields. An empty list indicates that the component has no configurable options + * but can still be used as default. + * If the component cannot be used without configuration, the promise will be rejected. + * + * @param uuid - The unique identifier of the component (source or parser). + * @param origin - The session context used to resolve the appropriate default options. + * @returns A promise that resolves to a list of fields (possibly empty) for automatic setup. + */ + public getDefaultOptions(uuid: string, origin: SessionAction): Promise { + return new Promise((resolve, reject) => { + Requests.IpcRequest.send( + Requests.Components.GetDefaultOptions.Response, + new Requests.Components.GetDefaultOptions.Request({ + uuid, + origin, + }), + ) + .then((response) => { + resolve(response.fields); + }) + .catch(reject); + }); + } + + /** + * Validates the provided configuration fields for the specified component. + * + * Validation is context-aware and may depend on the `SessionAction` (e.g., file vs. stream). + * The result is a map where each key corresponds to a field identifier, and the value + * is a human-readable error message describing the validation issue. + * + * @param origin - The session context (`SessionAction`) indicating the source type (e.g., file, stream). + * @param target - The identifier of the component whose settings are being validated. + * @param fields - An array of field values to be validated. + * @returns A promise that resolves to a map of field IDs to validation error messages. + * If a field is valid, it will not appear in the result. + */ + public validate( + origin: SessionAction, + target: string, + fields: Field[], + ): Promise> { + return new Promise((resolve, reject) => { + Requests.IpcRequest.send( + Requests.Components.Validate.Response, + new Requests.Components.Validate.Request({ + origin, + target, + fields, + }), + ) + .then((response) => { + resolve(response.errors); + }) + .catch(reject); + }); + } + + /** + * Provides access to the list of available components for the given session context. + * + * This includes both sources and parsers. The list may vary depending on the `SessionAction`, + * which defines the context (e.g., file-based vs. stream-based sessions). + * + * @param origin - The session context (`SessionAction`) determining which components are applicable. + * @returns An object with methods to retrieve available component identifiers. + */ + public get(origin: SessionAction): { + /** + * Retrieves a list of available source components. + * + * @returns A promise that resolves to an array of `Ident` objects representing source components. + */ + sources(): Promise; + + /** + * Retrieves a list of available parser components. + * + * @returns A promise that resolves to an array of `Ident` objects representing parser components. + */ + parsers(): Promise; + sourcesForParser(uuid: string): Promise; + } { + return { + sources: (): Promise => { + return new Promise((resolve, reject) => { + Requests.IpcRequest.send( + Requests.Components.GetSources.Response, + new Requests.Components.GetSources.Request({ + origin, + }), + ) + .then((response) => { + resolve(response.list); + }) + .catch(reject); + }); + }, + parsers: (): Promise => { + return new Promise((resolve, reject) => { + Requests.IpcRequest.send( + Requests.Components.GetParsers.Response, + new Requests.Components.GetParsers.Request({ + origin, + }), + ) + .then((response) => { + resolve(response.list); + }) + .catch(reject); + }); + }, + sourcesForParser: (uuid: string): Promise => { + return Promise.all([ + components + .get(origin) + .sources() + .catch((err: Error) => { + return Promise.reject( + new Error(`Fail to get sources list: ${err.message}`), + ); + }), + components + .get(origin) + .parsers() + .catch((err: Error) => { + return Promise.reject( + new Error(`Fail to get parsers list: ${err.message}`), + ); + }), + ]).then(([sources, parsers]: [Ident[], Ident[]]) => { + const parser = parsers.find((p) => p.uuid === uuid); + if (!parser) { + return Promise.reject(new Error(`Fail to find parser ${uuid}`)); + } + return sources.filter((source) => isIOCompatible(source.io, parser.io)); + }); + }, + }; + } + + /** + * Retrieves the identifier metadata for the specified component. + * + * The provided `uuid` should correspond to a registered component (e.g., parser or source). + * If the component exists, its `Ident` information will be returned; otherwise, the result + * will be `undefined`. + * + * @param uuid - The unique identifier of the component. + * @returns A promise that resolves to the `Ident` object describing the component, + * or `undefined` if no matching component is found. + */ + public getIdent(uuid: string): Promise { + return new Promise((resolve, reject) => { + Requests.IpcRequest.send( + Requests.Components.GetIdent.Response, + new Requests.Components.GetIdent.Request({ + target: uuid, + }), + ) + .then((response) => { + resolve(response.ident); + }) + .catch(reject); + }); + } +} + +export function isIOCompatible(a: IODataType, b: IODataType): boolean { + if (a === 'Any' || b === 'Any') { + return true; + } + if (typeof a === 'string' && typeof b === 'string') { + return a == b; + } + const aMtl: + | { + Multiple: Array; + } + | undefined = + typeof a === 'object' + ? (a as { + Multiple: Array; + }) + : undefined; + const bMtl: + | { + Multiple: Array; + } + | undefined = + typeof b === 'object' + ? (b as { + Multiple: Array; + }) + : undefined; + if (aMtl && bMtl) { + return aMtl.Multiple.find((a) => bMtl.Multiple.includes(a)) !== undefined; + } else if (aMtl) { + return aMtl.Multiple.includes(b); + } else if (bMtl) { + return bMtl.Multiple.includes(a); + } else { + return false; + } +} + +export interface Service extends Interface {} +export const components = register(new Service()); diff --git a/application/client/src/app/service/history/definition.file.ts b/application/client/src/app/service/history/definition.file.ts index 911819f9b..05c4d3cd3 100644 --- a/application/client/src/app/service/history/definition.file.ts +++ b/application/client/src/app/service/history/definition.file.ts @@ -1,8 +1,8 @@ import { getParentFolder } from '@platform/types/files'; import { bridge } from '@service/bridge'; +import { ObserveOperation } from '@service/session/dependencies/stream'; import * as obj from '@platform/env/obj'; -import * as $ from '@platform/types/observe'; export interface IFileDesc { extention: string; @@ -14,12 +14,12 @@ export interface IFileDesc { } export class FileDesc implements IFileDesc { - static async fromDataSource(source: $.Observe): Promise { - const file = source.origin.as<$.Origin.File.Configuration>($.Origin.File.Configuration); - if (file === undefined) { - return undefined; + static fromDataSource(operation: ObserveOperation): Promise { + const filename = operation.getOrigin().getFirstFilename(); + if (!filename || !operation.getOrigin().isFile()) { + return Promise.resolve(undefined); } - return FileDesc.fromFilename(file.filename()); + return FileDesc.fromFilename(filename); } static async fromFilename(filename: string): Promise { const file = (await bridge.files().getByPathWithCache([filename]))[0]; diff --git a/application/client/src/app/service/history/definition.concat.ts b/application/client/src/app/service/history/definition.files.ts similarity index 81% rename from application/client/src/app/service/history/definition.concat.ts rename to application/client/src/app/service/history/definition.files.ts index bd6443de1..519764068 100644 --- a/application/client/src/app/service/history/definition.concat.ts +++ b/application/client/src/app/service/history/definition.files.ts @@ -1,16 +1,14 @@ +import { ObserveOperation } from '@service/session/dependencies/stream'; import { IFileDesc, FileDesc } from './definition.file'; -import * as $ from '@platform/types/observe'; export class ConcatDesc { - static async fromDataSource(source: $.Observe): Promise { - const concat = source.origin.as<$.Origin.Concat.Configuration>( - $.Origin.Concat.Configuration, - ); - if (concat === undefined) { + static async fromDataSource(operation: ObserveOperation): Promise { + const files = operation.getOrigin().getFiles(); + if (files === undefined) { return undefined; } const list: IFileDesc[] = []; - for (const filename of concat.files()) { + for (const filename of files) { const desc = await FileDesc.fromFilename(filename); if (desc !== undefined) { list.push(desc); diff --git a/application/client/src/app/service/history/definition.stream.ts b/application/client/src/app/service/history/definition.stream.ts index 37d0e4b94..6a991f13f 100644 --- a/application/client/src/app/service/history/definition.stream.ts +++ b/application/client/src/app/service/history/definition.stream.ts @@ -1,26 +1,27 @@ -import * as $ from '@platform/types/observe'; +import { ObserveOperation } from '@service/session/dependencies/stream'; + import * as obj from '@platform/env/obj'; export interface IStreamDesc { - source: $.Origin.Stream.Stream.Source; + source: string; major: string; minor: string; } export class StreamDesc implements IStreamDesc { - static async fromDataSource(source: $.Observe): Promise { - const stream = source.origin.as<$.Origin.Stream.Configuration>( - $.Origin.Stream.Configuration, - ); - if (stream === undefined) { - return undefined; + static fromDataSource(operation: ObserveOperation): Promise { + if (!operation.getOrigin().isStream()) { + return Promise.resolve(undefined); } - const def = stream.desc(); - return { - major: def.major, - minor: def.minor, - source: stream.instance.instance.alias(), - }; + const descriptor = operation.getDescriptor(); + if (!descriptor) { + return Promise.resolve(undefined); + } + return Promise.resolve({ + major: descriptor.parser.name, + minor: descriptor.s_desc ? descriptor.s_desc : descriptor.source.name, + source: descriptor.source.name, + }); } static fromMinifiedStr( @@ -29,20 +30,20 @@ export class StreamDesc implements IStreamDesc { return src === undefined ? undefined : new StreamDesc({ - source: obj.getAsNotEmptyString(src, 's') as $.Origin.Stream.Stream.Source, + source: obj.getAsNotEmptyString(src, 's'), major: obj.getAsString(src, 'ma'), minor: obj.getAsString(src, 'mi'), }); } - public source: $.Origin.Stream.Stream.Source; - public major: string; - public minor: string; + public readonly source: string; + public readonly major: string; + public readonly minor: string; - constructor(definition: IStreamDesc) { - this.source = definition.source; - this.major = definition.major; - this.minor = definition.minor; + constructor(desc: IStreamDesc) { + this.source = desc.source; + this.major = desc.major; + this.minor = desc.minor; } public isSame(stream: StreamDesc): boolean { diff --git a/application/client/src/app/service/history/definition.ts b/application/client/src/app/service/history/definition.ts index 5044000e4..c993a9159 100644 --- a/application/client/src/app/service/history/definition.ts +++ b/application/client/src/app/service/history/definition.ts @@ -1,21 +1,20 @@ import { FileDesc, IFileDesc } from './definition.file'; import { StreamDesc, IStreamDesc } from './definition.stream'; -import { ConcatDesc } from './definition.concat'; +import { ConcatDesc } from './definition.files'; import { unique } from '@platform/env/sequence'; import { EntryConvertable, Entry } from '@platform/types/storage/entry'; import { error } from '@platform/log/utils'; import { Subject } from '@platform/env/subscription'; import { Equal } from '@platform/types/env/types'; import { Collections } from './collections'; +import { ObserveOperation } from '@service/session/dependencies/stream'; import * as obj from '@platform/env/obj'; -import * as $ from '@platform/types/observe'; export interface IDefinition { stream?: IStreamDesc; file?: IFileDesc; concat?: IFileDesc[]; - parser: $.Parser.Protocol; uuid: string; } @@ -23,21 +22,20 @@ export interface GroupRelations { rank: number; caption?: string; } + export class Definition implements EntryConvertable, Equal { static from(entry: Entry): Definition { return Definition.fromMinifiedStr(JSON.parse(entry.content)); } - static async fromDataSource(source: $.Observe): Promise { - const parser = source.parser.instance.alias(); - const file = await FileDesc.fromDataSource(source); - const concat = await ConcatDesc.fromDataSource(source); - const stream = await StreamDesc.fromDataSource(source); + static async fromDataSource(operation: ObserveOperation): Promise { + const file = await FileDesc.fromDataSource(operation); + const concat = await ConcatDesc.fromDataSource(operation); + const stream = await StreamDesc.fromDataSource(operation); if (file !== undefined) { return new Definition({ file, stream: undefined, concat: undefined, - parser, uuid: unique(), }); } else if (concat !== undefined) { @@ -45,7 +43,6 @@ export class Definition implements EntryConvertable, Equal { file: undefined, stream: undefined, concat, - parser, uuid: unique(), }); } else if (stream !== undefined) { @@ -53,7 +50,6 @@ export class Definition implements EntryConvertable, Equal { file: undefined, concat: undefined, stream, - parser, uuid: unique(), }); } else { @@ -65,7 +61,6 @@ export class Definition implements EntryConvertable, Equal { file: FileDesc.fromMinifiedStr(obj.getAsObjOrUndefined(src, 'f')), stream: StreamDesc.fromMinifiedStr(obj.getAsObjOrUndefined(src, 's')), concat: ConcatDesc.fromMinifiedStr(obj.getAsObjOrUndefined(src, 'c'))?.files, - parser: obj.getAsNotEmptyString(src, 'p') as $.Parser.Protocol, uuid: obj.getAsNotEmptyString(src, 'u'), }); if (def.file === undefined && def.stream === undefined && def.concat === undefined) { @@ -77,7 +72,6 @@ export class Definition implements EntryConvertable, Equal { public stream?: StreamDesc; public file?: FileDesc; public concat?: ConcatDesc; - public parser: $.Parser.Protocol; public uuid: string; constructor(definition: IDefinition) { @@ -86,7 +80,7 @@ export class Definition implements EntryConvertable, Equal { this.file = definition.file === undefined ? undefined : new FileDesc(definition.file); this.concat = definition.concat === undefined ? undefined : new ConcatDesc(definition.concat); - this.parser = definition.parser; + // this.parser = definition.parser; this.uuid = definition.uuid; } @@ -94,14 +88,14 @@ export class Definition implements EntryConvertable, Equal { this.stream = definition.stream; this.file = definition.file; this.concat = definition.concat; - this.parser = definition.parser; + // this.parser = definition.parser; this.uuid = definition.uuid; } public isSame(definition: Definition): boolean { - if (this.parser !== definition.parser) { - return false; - } + // if (this.parser !== definition.parser) { + // return false; + // } if (definition.stream !== undefined && this.stream !== undefined) { return this.stream.isSame(definition.stream); } @@ -123,9 +117,9 @@ export class Definition implements EntryConvertable, Equal { return collections.relations.indexOf(this.uuid) !== -1; }, suitable: (definition: Definition): GroupRelations | undefined => { - if (this.parser !== definition.parser) { - return undefined; - } + // if (this.parser !== definition.parser) { + // return undefined; + // } if (this.file !== undefined && definition.file !== undefined) { if ( (this.file.extention === definition.file.extention && @@ -167,7 +161,7 @@ export class Definition implements EntryConvertable, Equal { stream: this.stream, file: this.file, concat: this.concat === undefined ? undefined : this.concat.files, - parser: this.parser, + // parser: this.parser, uuid: this.uuid, }; if (def.file !== undefined) { @@ -199,7 +193,7 @@ export class Definition implements EntryConvertable, Equal { s: this.stream?.minify(), f: this.file?.minify(), c: this.concat?.minify(), - p: this.parser, + // p: this.parser, u: this.uuid, }; } diff --git a/application/client/src/app/service/history/session.ts b/application/client/src/app/service/history/session.ts index cef3e929e..0deb6ecfd 100644 --- a/application/client/src/app/service/history/session.ts +++ b/application/client/src/app/service/history/session.ts @@ -10,8 +10,7 @@ import { Subjects, Subject } from '@platform/env/subscription'; import { Suitable, SuitableGroup } from './suitable'; import { LockToken } from '@platform/env/lock.token'; import { cli } from '@service/cli'; - -import * as $ from '@platform/types/observe'; +import { ObserveOperation } from '@service/session/dependencies/stream'; export { Suitable, SuitableGroup }; @@ -25,7 +24,7 @@ export class HistorySession extends Subscriber { protected readonly sources: string[] = []; protected readonly globals: Subscriber = new Subscriber(); protected readonly locker: LockToken = new LockToken(true); - protected readonly pendings: $.Observe[] = []; + protected readonly pendings: ObserveOperation[] = []; protected checked: boolean = false; public readonly definitions: Definitions; @@ -62,12 +61,12 @@ export class HistorySession extends Subscriber { ); } - protected handleNewSource(source: $.Observe) { + protected handleNewSource(operation: ObserveOperation) { if (this.locker.isLocked()) { - this.pendings.push(source); + this.pendings.push(operation); return; } - Definition.fromDataSource(source) + Definition.fromDataSource(operation) .then((definition) => { definition = this.storage.definitions.update(definition); this.definitions.add(definition); diff --git a/application/client/src/app/service/hotkeys.ts b/application/client/src/app/service/hotkeys.ts index b0de1258e..1978612a5 100644 --- a/application/client/src/app/service/hotkeys.ts +++ b/application/client/src/app/service/hotkeys.ts @@ -124,7 +124,7 @@ export class Service extends Implementation { }, closeOnKey: '*', uuid: '?', - width: 500, + size: { width: 500 }, }); }), ); @@ -143,7 +143,7 @@ export class Service extends Implementation { horizontal: Horizontal.center, }, closeOnKey: 'Escape', - width: 350, + size: { width: 350 }, uuid: 'Ctrl + G', blur: false, }); @@ -161,7 +161,7 @@ export class Service extends Implementation { horizontal: Horizontal.center, }, closeOnKey: 'Escape', - width: 450, + size: { width: 450 }, uuid: 'Ctrl + P', }); }), diff --git a/application/client/src/app/service/ilc.ts b/application/client/src/app/service/ilc.ts index 81f1b0876..812cf718b 100644 --- a/application/client/src/app/service/ilc.ts +++ b/application/client/src/app/service/ilc.ts @@ -14,6 +14,7 @@ import { Logger } from '@platform/log'; import { session, Session, UnboundTab } from '@service/session'; import { state } from '@service/state'; import { jobs } from '@service/jobs'; +import { components } from '@service/components'; import { popup } from '@ui/service/popup'; import { notifications } from '@ui/service/notifications'; import { contextmenu } from '@ui/service/contextmenu'; @@ -71,6 +72,7 @@ export interface IlcInterface { @DependOn(favorites) @DependOn(sys) @DependOn(plugins) +@DependOn(components) // UI services @DependOn(sidebar) @DependOn(toolbar) diff --git a/application/client/src/app/service/jobs/job.ts b/application/client/src/app/service/jobs/job.ts index 3633e1c66..1c284567b 100644 --- a/application/client/src/app/service/jobs/job.ts +++ b/application/client/src/app/service/jobs/job.ts @@ -32,7 +32,7 @@ export class Job { this.session = validator.getAsNotEmptyStringOrAsUndefined(job, 'session'); this.name = validator.getAsNotEmptyStringOrAsUndefined(job, 'name'); this.desc = validator.getAsNotEmptyStringOrAsUndefined(job, 'desc'); - this.icon = validator.getAsNotEmptyStringOrAsUndefined(job, 'icon'); + this.icon = validator.getAsStringOrAsUndefined(job, 'icon'); this.progress = validator.getAsValidNumber(job, 'progress', { defaults: 0, max: 100, diff --git a/application/client/src/app/service/recent.ts b/application/client/src/app/service/recent.ts index 07116f3e5..00dc6b755 100644 --- a/application/client/src/app/service/recent.ts +++ b/application/client/src/app/service/recent.ts @@ -10,8 +10,7 @@ import { bridge } from '@service/bridge'; import { Action } from './recent/action'; import { error } from '@platform/log/utils'; import { Subject } from '@platform/env/subscription'; - -import * as $ from '@platform/types/observe'; +import { ObserveOperation } from './session/dependencies/stream'; const STORAGE_KEY = 'user_recent_actions'; @@ -38,30 +37,6 @@ export class Service extends Implementation { } }) .filter((a) => a !== undefined) as Action[]; - const invalid = actions.filter((a) => a.compatibility.invalidUuid !== undefined); - if (invalid.length > 0) { - await this.delete(invalid.map((a) => a.compatibility.invalidUuid as string)) - .then(() => { - this.log().debug( - `${invalid.length} actions with invalid UUIDs has been removed`, - ); - }) - .catch((err: Error) => { - this.log().error( - `Fail to remove recent actions with invalid uuid: ${err.message}`, - ); - }); - } - const converted = actions.filter((a) => a.compatibility.converted); - if (converted.length > 0) { - await this.update(converted) - .then(() => { - this.log().debug(`${converted.length} converted actions has been updated`); - }) - .catch((err: Error) => { - this.log().error(`Fail to update converted recent actions: ${err.message}`); - }); - } return actions; } @@ -71,7 +46,7 @@ export class Service extends Implementation { } const stored = await this.get(); actions.forEach((action) => { - const found = stored.find((a) => a.uuid === action.uuid); + const found = stored.find((a) => a.hash === action.hash); if (found === undefined) { return; } @@ -103,8 +78,11 @@ export class Service extends Implementation { }); } - public add(observe: $.Observe): Promise { - const action = new Action(observe); + public add(operation: ObserveOperation): Promise { + const action = Action.fromOperation(operation); + if (action instanceof Error) { + return Promise.reject(action); + } return this.update([action]); } } diff --git a/application/client/src/app/service/recent/action.ts b/application/client/src/app/service/recent/action.ts index f7965aa74..2b6f1a137 100644 --- a/application/client/src/app/service/recent/action.ts +++ b/application/client/src/app/service/recent/action.ts @@ -6,51 +6,102 @@ import { Stat, IStat } from './stat'; import { recent } from '@service/recent'; import { scope } from '@platform/env/scope'; import { Logger } from '@platform/log'; +import { SessionOrigin } from '@service/session/origin'; +import { ObserveOperation } from '@service/session/dependencies/stream'; +import { SessionDescriptor, SessionSetup } from '@platform/types/bindings'; +import { hash } from '@platform/env/hash'; -import * as $ from '@platform/types/observe'; -import * as compatibility from './compatibility'; +import * as obj from '@platform/env/obj'; interface IActionContent { + hash: string; stat: IStat; - observe: $.IObserve; + descriptor: SessionDescriptor; + setup: SessionSetup; } export class Action { static from(entry: Entry): Action | Error { - const action = new Action($.Observe.new()); - const error = action.entry().from(entry); - return error instanceof Error ? error : action; + try { + const body: IActionContent = JSON.parse(entry.content); + if (body.setup === undefined || typeof body.setup !== 'object') { + throw new Error(`No origin object. It might be old format. No converting support`); + } + if (!body.setup.origin) { + throw new Error( + `No origin in setup object. It might be old format. No converting support`, + ); + } + obj.getAsObj(body.setup, 'parser'); + obj.getAsObj(body.setup, 'source'); + if (body.descriptor === undefined || typeof body.descriptor !== 'object') { + throw new Error( + `No descriptor object. It might be old format. No converting support`, + ); + } + obj.getAsStringOrAsUndefined(body.descriptor, 'p_desc'); + obj.getAsStringOrAsUndefined(body.descriptor, 's_desc'); + obj.getAsObj(body.descriptor, 'parser'); + obj.getAsObj(body.descriptor, 'source'); + return new Action(body.setup, body.descriptor, body.stat); + } catch (err) { + return new Error(`Fail to parse action: ${error(err)}`); + } + } + + static getSetupHash(setup: SessionSetup): string { + return hash(JSON.stringify(setup)).toString(); + } + + static fromOperation(operation: ObserveOperation): Error | Action { + const setup = operation.getOrigin().getSessionSetup(); + const descriptor = operation.getDescriptor(); + if (!descriptor) { + return new Error( + `Action ${JSON.stringify(setup.origin)} doesn't have session descriptor`, + ); + } + return new Action(setup, descriptor); } protected logger: Logger; public stat: Stat = Stat.defaults(); - public uuid: string; - public compatibility: { - converted: boolean; - invalidUuid: string | undefined; - } = { - converted: false, - invalidUuid: undefined, - }; + public hash: string; - constructor(public observe: $.Observe) { - this.uuid = observe.signature(); - this.logger = scope.getLogger(`Action: ${this.uuid}`); + constructor( + public readonly setup: SessionSetup, + public readonly descriptor: SessionDescriptor, + stat?: IStat, + ) { + this.hash = Action.getSetupHash(this.setup); + this.logger = scope.getLogger(`Action: ${this.hash}`); + if (stat) { + this.stat = new Stat(stat); + } } - public isSuitable(observe?: $.Observe): boolean { - if (observe === undefined) { - return true; + public isSuitable(origin: SessionOrigin): boolean { + if (!origin.isSameAction(this.setup.origin)) { + return false; + } + if (!origin.components) { + return false; } - if (observe.origin.nature().alias() !== this.observe.origin.nature().alias()) { + if (!origin.components.parser || !origin.components.source) { return false; } - return this.observe.parser.alias() === observe.parser.alias(); + return ( + origin.components.parser.uuid === this.descriptor.parser.uuid && + origin.components.source.uuid === this.descriptor.source.uuid + ); } - public description(): $.Description.IOriginDetails { - return this.observe.origin.desc(); + public description(): { major: string; minor: string } { + return { + major: this.descriptor.s_desc ? this.descriptor.s_desc : this.descriptor.source.name, + minor: this.descriptor.p_desc ? this.descriptor.p_desc : this.descriptor.parser.name, + }; } public entry(): { @@ -58,32 +109,19 @@ export class Action { to(): Entry; } { return { - from: (entry: Entry): Error | undefined => { - try { - const body: IActionContent = JSON.parse(entry.content); - if (body.observe === undefined) { - // Check previous version (chipmunk <= 3.8.1) - this.observe = compatibility.from_3_8_1(entry); - this.compatibility.converted = true; - } else { - const observe = new $.Observe(body.observe); - this.observe = observe; - } - this.stat = Stat.from(body.stat); - this.uuid = this.observe.signature(); - this.compatibility.invalidUuid = - entry.uuid !== this.uuid ? entry.uuid : undefined; - return undefined; - } catch (err) { - return new Error(`Fail to parse action: ${error(err)}`); - } + from: (_entry: Entry): Error | undefined => { + return new Error( + `Instance method cannot be used on Action. Please use static methed instead.`, + ); }, to: (): Entry => { return { - uuid: this.uuid, + uuid: this.hash, content: JSON.stringify({ stat: this.stat.asObj(), - observe: this.observe.storable(), + descriptor: this.descriptor, + hash: this.hash, + setup: this.setup, } as IActionContent), }; }, @@ -91,31 +129,8 @@ export class Action { } public getActions(): { caption?: string; handler?: () => void }[] { - const observe = this.observe; - const configurable = observe.isConfigurable(); - const nature = observe.origin.nature().desc(); + const configurable = this.setup.parser.fields.length + this.setup.source.fields.length > 0; return [ - // **Note**: Recent actions isn't supported yet for plugins. Support of this - // feature will be included with general refactoring of Observe Configuration - // and a way to delivery available parser/source to client - ...(nature.type === $.Description.OriginType.plugin - ? [] - : [ - { - caption: ((): string => { - switch (nature.type) { - case $.Description.OriginType.file: - return 'Open with recent configuration'; - case $.Description.OriginType.net: - case $.Description.OriginType.serial: - return 'Connect with recent configuration'; - case $.Description.OriginType.command: - return 'Execute with recent configuration'; - } - })(), - handler: this.apply.bind(this), - }, - ]), ...(configurable ? [ { @@ -123,10 +138,12 @@ export class Action { handler: () => { session .initialize() - .configure(observe) + .configure( + SessionOrigin.fromSessionSetup(this.setup, this.descriptor), + ) .catch((err: Error) => { this.logger.error( - `Fail to configure observe object: ${err.message}`, + `Fail to configure session setup: ${err.message}`, ); }); }, @@ -137,7 +154,7 @@ export class Action { } public remove(): Promise { - return recent.delete([this.uuid]).catch((err: Error) => { + return recent.delete([this.hash]).catch((err: Error) => { this.logger.error(`Fail to remove recent action: ${err.message}`); }); } @@ -145,7 +162,7 @@ export class Action { public apply(): Promise { return session .initialize() - .auto(this.observe.locker().lock()) + .observe(SessionOrigin.fromSessionSetup(this.setup, this.descriptor)) .then(() => { return undefined; }) diff --git a/application/client/src/app/service/recent/compatibility.ts b/application/client/src/app/service/recent/compatibility.ts deleted file mode 100644 index 2fe1e9c08..000000000 --- a/application/client/src/app/service/recent/compatibility.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { Entry } from '@platform/types/storage/entry'; - -import * as $ from '@platform/types/observe'; -import * as Factory from '@platform/types/observe/factory'; - -// Interface belong to >= 3.8.1 -interface IDLTFilters { - app_ids?: string[]; - context_ids?: string[]; - ecu_ids?: string[]; -} - -// Interface belong to >= 3.8.1 -interface IDLTOptions { - logLevel: number; - filters: IDLTFilters; - fibex: string[]; - tz?: string; -} - -export function optionsToParserSettings( - options: IDLTOptions, - with_storage_header: boolean, - app_id_count: number, - context_id_count: number, -): $.Parser.Dlt.IConfiguration { - const filter_config: $.Parser.Dlt.IFilters = { - min_log_level: options.logLevel, - app_ids: options.filters.app_ids, - context_ids: options.filters.context_ids, - ecu_ids: options.filters.ecu_ids, - app_id_count, - context_id_count, - }; - return { - filter_config, - fibex_file_paths: options.fibex.length > 0 ? options.fibex : undefined, - with_storage_header, - tz: undefined, - }; -} - -// This function has to be removed since v 3.9.x or 3.10.x (after a couple of -// update iterations) -export function from_3_8_1(entry: Entry): $.Observe { - const action = JSON.parse(entry.content); - let observe; - if (action['file'] !== undefined) { - if (action['file']['dlt'] !== undefined) { - observe = new Factory.File() - .asDlt(optionsToParserSettings(action['file']['dlt'], true, 0, 0)) - .type($.Types.File.FileType.Binary) - .file(action['file']['filename']) - .get(); - } else if (action['file']['pcap'] !== undefined) { - observe = new Factory.File() - .asDlt(optionsToParserSettings(action['file']['pcap']['dlt'], true, 0, 0)) - .type($.Types.File.FileType.PcapNG) - .file(action['file']['filename']) - .get(); - } else { - observe = new Factory.File() - .asText() - .file(action['file']['filename']) - .type($.Types.File.FileType.Text) - .get(); - } - } else if (action['dlt_stream'] !== undefined) { - const defs = action['dlt_stream']; - const source = defs['source']; - const preconstructed = new Factory.Stream().asDlt( - optionsToParserSettings(defs['options'], false, 0, 0), - ); - if (source['process'] !== undefined) { - preconstructed.process(source['process']); - } else if (source['serial'] !== undefined) { - preconstructed.serial(source['serial']); - } else if (source['tcp'] !== undefined) { - preconstructed.tcp(source['tcp']); - } else if (source['udp'] !== undefined) { - preconstructed.udp(source['udp']); - } else { - throw new Error(`Unknonw type of source for stream.`); - } - observe = preconstructed.get(); - } else if (action['text_stream'] !== undefined) { - const defs = action['text_stream']; - const source = defs['source']; - const preconstructed = new Factory.Stream().asText(); - if (source['process'] !== undefined) { - preconstructed.process(source['process']); - } else if (source['serial'] !== undefined) { - preconstructed.serial(source['serial']); - } else if (source['tcp'] !== undefined) { - preconstructed.tcp(source['tcp']); - } else if (source['udp'] !== undefined) { - preconstructed.udp(source['udp']); - } else { - throw new Error(`Unknonw type of source for stream.`); - } - observe = preconstructed.get(); - } else { - throw new Error(`Unknonw type of action.`); - } - const error = observe.validate(); - if (error instanceof Error) { - throw error; - } - return observe; -} diff --git a/application/client/src/app/service/session.ts b/application/client/src/app/service/session.ts index 56e91b57c..82d29afc5 100644 --- a/application/client/src/app/service/session.ts +++ b/application/client/src/app/service/session.ts @@ -18,13 +18,11 @@ import { unique } from '@platform/env/sequence'; import { history } from '@service/history'; import { Render } from '@schema/render'; import { File } from '@platform/types/files'; -import { Observe } from '@platform/types/observe'; -import { getRender } from '@schema/render/tools'; -import { TabObserve } from '@tabs/observe/component'; -import { recent } from '@service/recent'; +import { SetupObserve } from '@tabs/setup/component'; import { bridge } from '@service/bridge'; - -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from './session/origin'; +import { popup, Vertical, Horizontal } from '@ui/service/popup'; +import { IApi } from '@ui/tabs/setup/state'; export { Session, TabControls, UnboundTab, Base }; @@ -226,9 +224,10 @@ export class Service extends Implementation { public initialize(): { suggest(filename: string, session?: Session): Promise; - auto(observe: Observe, session?: Session): Promise; - configure(observe: Observe, session?: Session): Promise; - observe(observe: Observe, session?: Session): Promise; + auto(origin: SessionOrigin, session?: Session): Promise; + configure(origin: SessionOrigin): Promise; + attach(session: Session, defSourceUuid: string | undefined): Promise; + observe(origin: SessionOrigin, session?: Session): Promise; multiple(files: File[]): Promise; } { return { @@ -236,95 +235,146 @@ export class Service extends Implementation { return bridge .files() .isBinary(filename) - .then((binary: boolean) => { - if (!binary) { - return this.initialize().observe( - new Factory.File() - .type(Factory.FileType.Text) - .asText() - .file(filename) - .get(), - session, - ); - } else { - return this.initialize().configure( - new Factory.File() - .type(Factory.FileType.Binary) - .file(filename) - .get(), - session, - ); - } + .then((_binary: boolean) => { + // TODO: considering binary or not should happen on rustcore now + return this.initialize().observe(SessionOrigin.file(filename), session); }); }, - auto: (observe: Observe, session?: Session): Promise => { - return observe.locker().is() - ? this.initialize().observe(observe, session) - : this.initialize().configure(observe, session); + auto: (origin: SessionOrigin, session?: Session): Promise => { + console.error(`Not implemented`); + return Promise.reject(new Error(`Not implemented`)); + // Okay. Here actually we need make auto-run for text files for example. This requires some addition + // API on backend. We should send SessionAction.File or SessionAction.Files and try to detect on backend + // is possible to open without options or not. It means we are checking: + // 1) is only one source and only one parser to open this file(s) or maybe stream. + // 2) does parser and source have some options/settings + // 3) if only one parser and only one source and no options/settings are requred - we make preselection + // + // This workflow lived before on front-end, now it should lives on rustcore side. + // + // return observe.locker().is() + // ? this.initialize().observe(observe, session) + // : this.initialize().configure(observe, session); }, - configure: (observe: Observe, session?: Session): Promise => { + configure: (origin: SessionOrigin): Promise => { return new Promise((resolve) => { + const inputs: { + origin: SessionOrigin; + api: IApi; + } = { + origin, + api: { + finish: (origin: SessionOrigin): Promise => { + return new Promise((success, failed) => { + this.initialize() + .observe(origin) + .then((session) => { + success(session); + api?.close(); + resolve(session); + }) + .catch((err: Error) => { + failed(err); + }); + }); + }, + cancel: (): void => { + api?.close(); + resolve(undefined); + }, + }, + }; const api = this.add().tab({ - name: observe.origin.title(), + name: origin.getTitle(), content: { - factory: TabObserve, - inputs: TabObserve.inputs({ - observe, - api: { - finish: (observe: Observe): Promise => { - return new Promise((success, failed) => { - this.initialize() - .observe(observe, session) - .then((session) => { - success(); - api?.close(); - resolve(session); - }) - .catch((err: Error) => { - failed(err); - }); - }); - }, - cancel: (): void => { - api?.close(); - resolve(undefined); - }, - tab: (): TabControls => { - return api as unknown as TabControls; - }, - }, - }), + factory: SetupObserve, + inputs, }, active: true, }); }); }, - observe: async (observe: Observe, existed?: Session): Promise => { - const render = await getRender(observe); - if (render instanceof Error) { - throw render; + attach: ( + session: Session, + defSourceUuid: string | undefined, + ): Promise => { + const origin = session.stream.getOrigin(); + if (!origin) { + return Promise.reject(new Error(`No origin to attach new one`)); + } + const parser = origin.components?.parser?.uuid; + if (!parser) { + return Promise.reject(new Error(`No original parser to attach new one`)); } + const source = origin.components?.source?.uuid; + if (!source) { + return Promise.reject(new Error(`No original source to attach new one`)); + } + return new Promise((resolve) => { + const inputs: { + origin: SessionOrigin; + parser: string; + source: string; + api: IApi; + } = { + origin, + parser, + source: defSourceUuid ? defSourceUuid : source, + api: { + finish: (origin: SessionOrigin): Promise => { + return new Promise((success, failed) => { + session.stream + .observe() + .start(origin) + .then((session) => { + success(session); + instance.close(); + resolve(session); + }) + .catch((err: Error) => { + failed(err); + }); + }); + }, + cancel: (): void => { + instance.close(); + resolve(undefined); + }, + }, + }; + const instance = popup.open({ + component: { + factory: SetupObserve, + inputs, + }, + position: { + vertical: Vertical.center, + horizontal: Horizontal.center, + }, + size: { + height: 0.8, + width: 0.9, + }, + closeOnKey: 'Escape', + uuid: session.uuid(), + }); + }); + }, + observe: async (origin: SessionOrigin, existed?: Session): Promise => { + const render = await origin.getRender(); const session = existed !== undefined ? existed : await this.add(false).empty(render); return new Promise((resolve, reject) => { session.stream .observe() - .start(observe) + .start(origin) .then((uuid: string) => { - if (existed === undefined) { - const error = this.bind( - session.uuid(), - observe.origin.desc().major, - true, - ); - if (error instanceof Error) { - this.log().error(`Fail to bind session: ${error.message}`); - } - recent.add(observe).catch((err: Error) => { - this.log().error( - `Fail to save action as recent: ${err.message}`, - ); - }); + const error = + existed === undefined + ? this.bind(session.uuid(), uuid, true) + : undefined; + if (error instanceof Error) { + this.log().error(`Fail to bind session: ${error.message}`); } resolve(uuid); }) @@ -351,14 +401,12 @@ export class Service extends Implementation { inputs: { files, setTitle: (title: string) => { - api?.setTitle(title); + api && api.setTitle(title); }, - done: (observe: Observe) => { + done: (origin: SessionOrigin) => { this.initialize() - .observe(observe) - .then((session) => { - resolve(session); - }) + .observe(origin) + .then(resolve) .catch((err: Error) => { this.log().error( `Fail to setup observe: ${err.message}`, @@ -366,11 +414,11 @@ export class Service extends Implementation { reject(err); }) .finally(() => { - api?.close(); + api && api.close(); }); }, cancel: () => { - api?.close(); + api && api.close(); resolve(undefined); }, }, diff --git a/application/client/src/app/service/session/dependencies/cli.ts b/application/client/src/app/service/session/dependencies/cli.ts index f48da0d90..ea0b119d3 100644 --- a/application/client/src/app/service/session/dependencies/cli.ts +++ b/application/client/src/app/service/session/dependencies/cli.ts @@ -6,176 +6,174 @@ import { getFileName, getParentFolder } from '@platform/types/files'; import { env } from '@service/env'; import { cli } from '@service/cli'; -import * as $ from '@platform/types/observe'; - @SetupLogger() export class Cli extends Subscriber { protected session!: Session; protected _cmd: string = './chipmunk'; - protected source(): { - stream(stream: $.Origin.Stream.Configuration): string | undefined; - file(file: $.Origin.File.Configuration): string | undefined; - concat(concat: $.Origin.Concat.Configuration): string | undefined; - from(observed: $.Observe[]): string | undefined; - cx(observed: $.Observe[]): $.Origin.Context | undefined; - } { - return { - stream: (stream: $.Origin.Stream.Configuration): string | undefined => { - const serial = stream.as<$.Origin.Stream.Stream.Serial.Configuration>( - $.Origin.Stream.Stream.Serial.Configuration, - ); - const tcp = stream.as<$.Origin.Stream.Stream.TCP.Configuration>( - $.Origin.Stream.Stream.TCP.Configuration, - ); - const udp = stream.as<$.Origin.Stream.Stream.UDP.Configuration>( - $.Origin.Stream.Stream.UDP.Configuration, - ); - const process = stream.as<$.Origin.Stream.Stream.Process.Configuration>( - $.Origin.Stream.Stream.Process.Configuration, - ); - if (serial !== undefined) { - return `streams --serial "\ -${serial.configuration.path};\ -${serial.configuration.baud_rate};\ -${serial.configuration.data_bits};\ -${serial.configuration.flow_control};\ -${serial.configuration.parity};\ -${serial.configuration.stop_bits}"`; - } else if (tcp !== undefined) { - return `streams --tcp "${tcp.configuration.bind_addr}"`; - } else if (udp !== undefined) { - return `streams --udp "\ -${udp.configuration.bind_addr}|\ -${udp.configuration.multicast[0].multiaddr},\ -${udp.configuration.multicast[0].interface};"`; - } else if (process !== undefined) { - return `streams --stdout "${process.configuration.command}"`; - } else { - return undefined; - } - }, - file: (file: $.Origin.File.Configuration): string | undefined => { - return `-o ${this.filename(file.get().filename())}`; - }, - concat: (concat: $.Origin.Concat.Configuration): string | undefined => { - const relative = - Array.from(new Set(concat.files().map((f) => getParentFolder(f)))).length === 1; - return concat - .files() - .map((f) => `-o ${relative ? this.filename(f) : f}`) - .join(' '); - }, - from: (observed: $.Observe[]): string | undefined => { - const get = (observe: $.Observe): string | undefined => { - const stream = observe.origin.as<$.Origin.Stream.Configuration>( - $.Origin.Stream.Configuration, - ); - const file = observe.origin.as<$.Origin.File.Configuration>( - $.Origin.File.Configuration, - ); - const concat = observe.origin.as<$.Origin.Concat.Configuration>( - $.Origin.Concat.Configuration, - ); - if (stream !== undefined) { - return this.source().stream(stream); - } else if (file !== undefined) { - return this.source().file(file); - } else if (concat !== undefined) { - return this.source().concat(concat); - } else { - return undefined; - } - }; - if (observed.length === 0) { - return undefined; - } - let args = ''; - for (const observe of observed) { - const arg = get(observe); - if (arg === undefined) { - return undefined; - } - args = `${args}${args.length === 0 ? '' : ' '}${arg}`; - } - return args; - }, - cx(observed: $.Observe[]): $.Origin.Context | undefined { - function get(observe: $.Observe): $.Origin.Context | undefined { - if ( - observe.origin.as<$.Origin.Stream.Configuration>( - $.Origin.Stream.Configuration, - ) !== undefined - ) { - return $.Origin.Context.Stream; - } else if ( - observe.origin.as<$.Origin.File.Configuration>( - $.Origin.File.Configuration, - ) !== undefined - ) { - return $.Origin.Context.File; - } else if ( - observe.origin.as<$.Origin.Concat.Configuration>( - $.Origin.Concat.Configuration, - ) !== undefined - ) { - return $.Origin.Context.Concat; - } else { - return undefined; - } - } - if (observed.length === 0 || get(observed[0]) === undefined) { - return undefined; - } - const first: $.Origin.Context = get(observed[0]) as unknown as $.Origin.Context; - for (const observe of observed) { - if (first !== get(observe)) { - return undefined; - } - } - return first; - }, - }; - } + // protected source(): { + // stream(stream: $.Origin.Stream.Configuration): string | undefined; + // file(file: $.Origin.File.Configuration): string | undefined; + // concat(concat: $.Origin.Concat.Configuration): string | undefined; + // from(observed: $.Observe[]): string | undefined; + // cx(observed: $.Observe[]): $.Origin.Context | undefined; + // } { + // return { + // stream: (stream: $.Origin.Stream.Configuration): string | undefined => { + // const serial = stream.as<$.Origin.Stream.Stream.Serial.Configuration>( + // $.Origin.Stream.Stream.Serial.Configuration, + // ); + // const tcp = stream.as<$.Origin.Stream.Stream.TCP.Configuration>( + // $.Origin.Stream.Stream.TCP.Configuration, + // ); + // const udp = stream.as<$.Origin.Stream.Stream.UDP.Configuration>( + // $.Origin.Stream.Stream.UDP.Configuration, + // ); + // const process = stream.as<$.Origin.Stream.Stream.Process.Configuration>( + // $.Origin.Stream.Stream.Process.Configuration, + // ); + // if (serial !== undefined) { + // return `streams --serial "\ + // ${serial.configuration.path};\ + // ${serial.configuration.baud_rate};\ + // ${serial.configuration.data_bits};\ + // ${serial.configuration.flow_control};\ + // ${serial.configuration.parity};\ + // ${serial.configuration.stop_bits}"`; + // } else if (tcp !== undefined) { + // return `streams --tcp "${tcp.configuration.bind_addr}"`; + // } else if (udp !== undefined) { + // return `streams --udp "\ + // ${udp.configuration.bind_addr}|\ + // ${udp.configuration.multicast[0].multiaddr},\ + // ${udp.configuration.multicast[0].interface};"`; + // } else if (process !== undefined) { + // return `streams --stdout "${process.configuration.command}"`; + // } else { + // return undefined; + // } + // }, + // file: (file: $.Origin.File.Configuration): string | undefined => { + // return `-o ${this.filename(file.get().filename())}`; + // }, + // concat: (concat: $.Origin.Concat.Configuration): string | undefined => { + // const relative = + // Array.from(new Set(concat.files().map((f) => getParentFolder(f)))).length === 1; + // return concat + // .files() + // .map((f) => `-o ${relative ? this.filename(f) : f}`) + // .join(' '); + // }, + // from: (observed: $.Observe[]): string | undefined => { + // const get = (observe: $.Observe): string | undefined => { + // const stream = observe.origin.as<$.Origin.Stream.Configuration>( + // $.Origin.Stream.Configuration, + // ); + // const file = observe.origin.as<$.Origin.File.Configuration>( + // $.Origin.File.Configuration, + // ); + // const concat = observe.origin.as<$.Origin.Concat.Configuration>( + // $.Origin.Concat.Configuration, + // ); + // if (stream !== undefined) { + // return this.source().stream(stream); + // } else if (file !== undefined) { + // return this.source().file(file); + // } else if (concat !== undefined) { + // return this.source().concat(concat); + // } else { + // return undefined; + // } + // }; + // if (observed.length === 0) { + // return undefined; + // } + // let args = ''; + // for (const observe of observed) { + // const arg = get(observe); + // if (arg === undefined) { + // return undefined; + // } + // args = `${args}${args.length === 0 ? '' : ' '}${arg}`; + // } + // return args; + // }, + // cx(observed: $.Observe[]): $.Origin.Context | undefined { + // function get(observe: $.Observe): $.Origin.Context | undefined { + // if ( + // observe.origin.as<$.Origin.Stream.Configuration>( + // $.Origin.Stream.Configuration, + // ) !== undefined + // ) { + // return $.Origin.Context.Stream; + // } else if ( + // observe.origin.as<$.Origin.File.Configuration>( + // $.Origin.File.Configuration, + // ) !== undefined + // ) { + // return $.Origin.Context.File; + // } else if ( + // observe.origin.as<$.Origin.Concat.Configuration>( + // $.Origin.Concat.Configuration, + // ) !== undefined + // ) { + // return $.Origin.Context.Concat; + // } else { + // return undefined; + // } + // } + // if (observed.length === 0 || get(observed[0]) === undefined) { + // return undefined; + // } + // const first: $.Origin.Context = get(observed[0]) as unknown as $.Origin.Context; + // for (const observe of observed) { + // if (first !== get(observe)) { + // return undefined; + // } + // } + // return first; + // }, + // }; + // } - protected parser(): { - text(parser: $.Parser.Text.Configuration): string | undefined; - dlt(parser: $.Parser.Dlt.Configuration): string | undefined; - someip(parser: $.Parser.SomeIp.Configuration): string | undefined; - from(observe: $.Observe): string | undefined; - } { - return { - text: (_parser: $.Parser.Text.Configuration): string | undefined => { - return `--parser text`; - }, - dlt: (_parser: $.Parser.Dlt.Configuration): string | undefined => { - return `--parser dlt`; - }, - someip: (_parser: $.Parser.SomeIp.Configuration): string | undefined => { - return `--parser someip`; - }, - from: (observe: $.Observe): string | undefined => { - const text = observe.parser.as<$.Parser.Text.Configuration>( - $.Parser.Text.Configuration, - ); - const dlt = observe.parser.as<$.Parser.Dlt.Configuration>( - $.Parser.Dlt.Configuration, - ); - const someip = observe.parser.as<$.Parser.SomeIp.Configuration>( - $.Parser.SomeIp.Configuration, - ); - if (text !== undefined) { - return this.parser().text(text); - } else if (dlt !== undefined) { - return this.parser().dlt(dlt); - } else if (someip !== undefined) { - return this.parser().someip(someip); - } else { - return undefined; - } - }, - }; - } + // protected parser(): { + // text(parser: $.Parser.Text.Configuration): string | undefined; + // dlt(parser: $.Parser.Dlt.Configuration): string | undefined; + // someip(parser: $.Parser.SomeIp.Configuration): string | undefined; + // from(observe: $.Observe): string | undefined; + // } { + // return { + // text: (_parser: $.Parser.Text.Configuration): string | undefined => { + // return `--parser text`; + // }, + // dlt: (_parser: $.Parser.Dlt.Configuration): string | undefined => { + // return `--parser dlt`; + // }, + // someip: (_parser: $.Parser.SomeIp.Configuration): string | undefined => { + // return `--parser someip`; + // }, + // from: (observe: $.Observe): string | undefined => { + // const text = observe.parser.as<$.Parser.Text.Configuration>( + // $.Parser.Text.Configuration, + // ); + // const dlt = observe.parser.as<$.Parser.Dlt.Configuration>( + // $.Parser.Dlt.Configuration, + // ); + // const someip = observe.parser.as<$.Parser.SomeIp.Configuration>( + // $.Parser.SomeIp.Configuration, + // ); + // if (text !== undefined) { + // return this.parser().text(text); + // } else if (dlt !== undefined) { + // return this.parser().dlt(dlt); + // } else if (someip !== undefined) { + // return this.parser().someip(someip); + // } else { + // return undefined; + // } + // }, + // }; + // } protected filters(): string { const filters = this.session.search.store().filters().get(); @@ -226,26 +224,28 @@ ${udp.configuration.multicast[0].interface};"`; } protected arguments(): string | undefined { - const observed = this.session.stream - .observe() - .sources() - .map((s) => s.observe); - if (observed.length === 0) { - return undefined; - } - if (this.source().cx(observed) === undefined) { - // Not the same origin - return undefined; - } - const source = this.source().from(observed); - if (source === undefined) { - return undefined; - } - const parser = this.parser().from(observed[0]); - if (parser === undefined) { - return undefined; - } - return `${source} ${parser} ${this.filters()}`; + console.error(`Not implemented`); + // const operations = this.session.stream + // .observe() + // .operations() + // .map((s) => s.isRunning()); + // if (operations.length === 0) { + // return undefined; + // } + // if (this.source().cx(operations) === undefined) { + // // Not the same origin + // return undefined; + // } + // const source = this.source().from(operations); + // if (source === undefined) { + // return undefined; + // } + // const parser = this.parser().from(operations[0]); + // if (parser === undefined) { + // return undefined; + // } + // return `${source} ${parser} ${this.filters()}`; + return; } public init(session: Session) { diff --git a/application/client/src/app/service/session/dependencies/comments/index.ts b/application/client/src/app/service/session/dependencies/comments/index.ts index c0cd9101e..2f9ae9868 100644 --- a/application/client/src/app/service/session/dependencies/comments/index.ts +++ b/application/client/src/app/service/session/dependencies/comments/index.ts @@ -434,7 +434,7 @@ export class Comments extends Subscriber { horizontal: Horizontal.center, }, closeOnKey: 'Escape', - width: 450, + size: { width: 450 }, uuid: 'add_new_comment', }); } diff --git a/application/client/src/app/service/session/dependencies/info.ts b/application/client/src/app/service/session/dependencies/info.ts index db00e42c1..08db86f16 100644 --- a/application/client/src/app/service/session/dependencies/info.ts +++ b/application/client/src/app/service/session/dependencies/info.ts @@ -1,8 +1,7 @@ import { Subject } from '@platform/env/subscription'; import { unique } from '@platform/env/sequence'; -import { Observe } from '@platform/types/observe'; -import { Protocol } from '@platform/types/observe/parser'; import { getFileName } from '@platform/types/files'; +import { ObserveOperation } from './stream'; export interface IInfoBlock { caption: string; @@ -19,62 +18,24 @@ export class Info { this.updated.destroy(); } - public fromObserveInfo(observe: Observe) { - const uuid = observe.hash().toString(); - if (observe.configuration.parser[Protocol.Dlt] !== undefined) { - const cfg = observe.configuration.parser[Protocol.Dlt]; - if (cfg.fibex_file_paths !== undefined) { - if (cfg.fibex_file_paths.length === 1) { - this.add( - { - caption: `FIBEX: ${getFileName(cfg.fibex_file_paths[0])}`, - tooltip: `Used FIBEX: ${cfg.fibex_file_paths[0]}`, - }, - `fibex_${uuid}`, - ); - } else if (cfg.fibex_file_paths.length > 1) { - this.add( - { - caption: `FIBEX: ${cfg.fibex_file_paths.length} files`, - tooltip: cfg.fibex_file_paths.join('\n'), - }, - `fibex_${uuid}`, - ); - } - } - if (cfg.tz !== undefined) { - this.add( - { - caption: `TZ: ${cfg.tz}`, - tooltip: `Used TimeZone: ${cfg.tz}`, - }, - `tz_${uuid}`, - ); - } - } else if (observe.configuration.parser[Protocol.SomeIp] !== undefined) { - const cfg = observe.configuration.parser[Protocol.SomeIp]; - if (cfg.fibex_file_paths !== undefined) { - if (cfg.fibex_file_paths.length === 1) { - this.add( - { - caption: `FIBEX: ${getFileName(cfg.fibex_file_paths[0])}`, - tooltip: `Used FIBEX: ${cfg.fibex_file_paths[0]}`, - }, - `fibex_${uuid}`, - ); - } else if (cfg.fibex_file_paths.length > 1) { - this.add( - { - caption: `FIBEX: ${cfg.fibex_file_paths.length} files`, - tooltip: cfg.fibex_file_paths.join('\n'), - }, - `fibex_${uuid}`, - ); - } - } - } else if (observe.configuration.parser[Protocol.Text] !== undefined) { - // ignore - } + public fromOperation(operation: ObserveOperation) { + // TODO (Not implemented. API required): + // + // Purpose of this service + // + // When a session is created, auxiliary information may be displayed in the bottom toolbar. + // For example, for DLT and SomeIP files (or TCP/UDP connections), this could be information + // about the associated FIBEX file. + // + // In the previous version, the client had knowledge about the used parser/source, + // so this information was generated directly on the client side. + // + // Due to the updated paradigm, where the client "knows nothing" about the parsers + // and sources being used, an additional API on the rustcore side is required. + // This API provides the "messages" that need to be displayed in the toolbar. + // + // Currently, this functionality is disabled. + console.warn(`API for toolbar notes is not implemented yet`); } public add(block: IInfoBlock, uuid?: string): string { diff --git a/application/client/src/app/service/session/dependencies/observing/implementations/files.ts b/application/client/src/app/service/session/dependencies/observing/implementations/files.ts deleted file mode 100644 index cb25503f4..000000000 --- a/application/client/src/app/service/session/dependencies/observing/implementations/files.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { Provider as Base } from '../provider'; -import { ObserveSource } from '@service/session/dependencies/observing/source'; -import { IComponentDesc } from '@elements/containers/dynamic/component'; -import { List } from '@ui/views/sidebar/observe/lists/file/component'; -import { IMenuItem } from '@ui/service/contextmenu'; - -import * as $ from '@platform/types/observe'; - -export class Provider extends Base { - private _sources: ObserveSource[] = []; - private _processed: string[] = []; - - public contextMenu(source: ObserveSource): IMenuItem[] { - const items: IMenuItem[] = []; - const observer = source.observer; - if (observer !== undefined) { - items.push({ - caption: 'Stop tailing', - handler: () => { - observer.abort().catch((err: Error) => { - this.logger.error(`Fail to abort observing (file tailing): ${err.message}`); - }); - }, - }); - } - return items; - } - - public update(sources: ObserveSource[]): Provider { - const added = sources.filter((s) => !this._processed.includes(s.uuid())); - // Add files - this._sources.push( - ...added.filter( - (source) => source.observe.origin.nature() instanceof $.Origin.File.Configuration, - ), - ); - // Add files from concat-container - added.map((source) => { - const concat = source.observe.origin.as<$.Origin.Concat.Configuration>( - $.Origin.Concat.Configuration, - ); - if (concat === undefined) { - return; - } - const origins = concat.asFileOrigins(); - const parser = source.observe.parser.sterilized(); - const files = origins.map((origin) => { - return new ObserveSource(new $.Observe({ origin, parser })).asChild(); - }); - this._sources.push(...files); - }); - this._processed.push(...added.map((a) => a.uuid())); - Base.overwrite(sources, this._sources); - return this; - } - - public sources(): ObserveSource[] { - return this._sources; - } - - public getPanels(): { - list(): { - name(): string; - desc(): string; - comp(): IComponentDesc; - }; - nocontent(): { - name(): string | undefined; - desc(): string | undefined; - comp(): IComponentDesc | undefined; - }; - } { - return { - list: (): { - name(): string; - desc(): string; - comp(): IComponentDesc; - } => { - return { - name: (): string => { - return `Files`; - }, - desc: (): string => { - return this._sources.length > 0 ? this._sources.length.toString() : ''; - }, - comp: (): IComponentDesc => { - return { - factory: List, - inputs: { - provider: this, - }, - }; - }, - }; - }, - - nocontent: (): { - name(): string | undefined; - desc(): string | undefined; - comp(): IComponentDesc | undefined; - } => { - return { - name: (): string | undefined => { - return undefined; - }, - desc: (): string | undefined => { - return undefined; - }, - comp: (): IComponentDesc | undefined => { - return undefined; - }, - }; - }, - }; - } - - public override getNewSourceError(): Error | undefined { - const active = this._sources.find((s) => s.observer !== undefined); - if (active !== undefined) { - return new Error(`Cannot attach new source while file is tailing`); - } - if (this._sources.length !== 1) { - return undefined; - } - const singleTextFile = this._sources.find((s) => { - const file = s.observe.origin.as<$.Origin.File.Configuration>( - $.Origin.File.Configuration, - ); - if (file === undefined) { - return false; - } - if (file.filetype() === $.Types.File.FileType.Text && !s.child) { - return true; - } - return false; - }); - if (singleTextFile !== undefined) { - return new Error( - `Single file has been opened. In this case you cannot attach new source(s).`, - ); - } - return undefined; - } -} diff --git a/application/client/src/app/service/session/dependencies/observing/implementations/index.ts b/application/client/src/app/service/session/dependencies/observing/implementations/index.ts deleted file mode 100644 index 20d15fb76..000000000 --- a/application/client/src/app/service/session/dependencies/observing/implementations/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Provider as Files } from './files'; -import { Provider as Processes } from './processes'; -import { Provider as Serial } from './serial'; -import { Provider as Tcp } from './tcp'; -import { Provider as Udp } from './udp'; - -export const PROVIDERS = [Files, Processes, Serial, Tcp, Udp]; diff --git a/application/client/src/app/service/session/dependencies/observing/implementations/processes.ts b/application/client/src/app/service/session/dependencies/observing/implementations/processes.ts deleted file mode 100644 index bb6dec2e9..000000000 --- a/application/client/src/app/service/session/dependencies/observing/implementations/processes.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { Provider as Base } from '../provider'; -import { ObserveSource } from '@service/session/dependencies/observing/source'; -import { IComponentDesc } from '@elements/containers/dynamic/component'; -import { List } from '@ui/views/sidebar/observe/lists/process/component'; -import { IMenuItem } from '@ui/service/contextmenu'; -import { session } from '@service/session'; - -import * as $ from '@platform/types/observe'; -import * as Factory from '@platform/types/observe/factory'; - -export class Provider extends Base { - private _sources: ObserveSource[] = []; - - public contextMenu(source: ObserveSource): IMenuItem[] { - const items: IMenuItem[] = []; - const observer = source.observer; - if (observer !== undefined) { - items.push({ - caption: 'Stop', - handler: () => { - observer.abort().catch((err: Error) => { - this.logger.error(`Fail to abort observing (spawning): ${err.message}`); - }); - }, - }); - } else { - items.push({ - caption: 'Repeat Command', - handler: () => { - this.clone(source.observe).catch((err: Error) => { - this.logger.error(`Fail to repeat: ${err.message}`); - }); - }, - }); - } - return items; - } - - public update(sources: ObserveSource[]): Provider { - this._sources = sources.filter( - (source) => - source.observe.origin.nature() instanceof - $.Origin.Stream.Stream.Process.Configuration, - ); - return this; - } - - public openNewSessionOptions() { - session.initialize().configure(new Factory.Stream().process().get()); - } - - public sources(): ObserveSource[] { - return this._sources; - } - - public getPanels(): { - list(): { - name(): string; - desc(): string; - comp(): IComponentDesc; - }; - nocontent(): { - name(): string | undefined; - desc(): string | undefined; - comp(): IComponentDesc | undefined; - }; - } { - return { - list: (): { - name(): string; - desc(): string; - comp(): IComponentDesc; - } => { - return { - name: (): string => { - return `Commands`; - }, - desc: (): string => { - return this._sources.length > 0 ? this._sources.length.toString() : ''; - }, - comp: (): IComponentDesc => { - return { - factory: List, - inputs: { - provider: this, - }, - }; - }, - }; - }, - nocontent: (): { - name(): string | undefined; - desc(): string | undefined; - comp(): IComponentDesc | undefined; - } => { - return { - name: (): string | undefined => { - return undefined; - }, - desc: (): string | undefined => { - return undefined; - }, - comp: (): IComponentDesc | undefined => { - return undefined; - }, - }; - }, - }; - } -} diff --git a/application/client/src/app/service/session/dependencies/observing/implementations/serial.ts b/application/client/src/app/service/session/dependencies/observing/implementations/serial.ts deleted file mode 100644 index 63c12383a..000000000 --- a/application/client/src/app/service/session/dependencies/observing/implementations/serial.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { Provider as Base } from '../provider'; -import { ObserveSource } from '@service/session/dependencies/observing/source'; -import { IComponentDesc } from '@elements/containers/dynamic/component'; -import { List } from '@ui/views/sidebar/observe/lists/serial/component'; -import { IMenuItem } from '@ui/service/contextmenu'; -import { session } from '@service/session'; - -import * as $ from '@platform/types/observe'; -import * as Factory from '@platform/types/observe/factory'; - -export class Provider extends Base { - private _sources: ObserveSource[] = []; - - public contextMenu(source: ObserveSource): IMenuItem[] { - const items: IMenuItem[] = []; - const observer = source.observer; - if (observer !== undefined) { - items.push({ - caption: 'Disconnect', - handler: () => { - observer.abort().catch((err: Error) => { - this.logger.error( - `Fail to abort observing (serial listening): ${err.message}`, - ); - }); - }, - }); - } else { - items.push({ - caption: 'Connect', - handler: () => { - this.clone(source.observe).catch((err: Error) => { - this.logger.error(`Fail to repeat: ${err.message}`); - }); - }, - }); - } - return items; - } - - public openNewSessionOptions() { - session.initialize().configure(new Factory.Stream().serial().get()); - } - - public update(sources: ObserveSource[]): Provider { - this._sources = sources.filter( - (source) => - source.observe.origin.nature() instanceof - $.Origin.Stream.Stream.Serial.Configuration, - ); - return this; - } - - public sources(): ObserveSource[] { - return this._sources; - } - - public getPanels(): { - list(): { - name(): string; - desc(): string; - comp(): IComponentDesc; - }; - nocontent(): { - name(): string | undefined; - desc(): string | undefined; - comp(): IComponentDesc | undefined; - }; - } { - return { - list: (): { - name(): string; - desc(): string; - comp(): IComponentDesc; - } => { - return { - name: (): string => { - return `Serial Connections`; - }, - desc: (): string => { - return this._sources.length > 0 ? this._sources.length.toString() : ''; - }, - comp: (): IComponentDesc => { - return { - factory: List, - inputs: { - provider: this, - }, - }; - }, - }; - }, - nocontent: (): { - name(): string | undefined; - desc(): string | undefined; - comp(): IComponentDesc | undefined; - } => { - return { - name: (): string | undefined => { - return undefined; - }, - desc: (): string | undefined => { - return undefined; - }, - comp: (): IComponentDesc | undefined => { - return undefined; - }, - }; - }, - }; - } -} diff --git a/application/client/src/app/service/session/dependencies/observing/implementations/tcp.ts b/application/client/src/app/service/session/dependencies/observing/implementations/tcp.ts deleted file mode 100644 index 03c7ae882..000000000 --- a/application/client/src/app/service/session/dependencies/observing/implementations/tcp.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { Provider as Base } from '../provider'; -import { ObserveSource } from '@service/session/dependencies/observing/source'; -import { IComponentDesc } from '@elements/containers/dynamic/component'; -import { List } from '@ui/views/sidebar/observe/lists/tcp/component'; -import { IMenuItem } from '@ui/service/contextmenu'; -import { session } from '@service/session'; - -import * as $ from '@platform/types/observe'; -import * as Factory from '@platform/types/observe/factory'; - -export class Provider extends Base { - private _sources: ObserveSource[] = []; - - public contextMenu(source: ObserveSource): IMenuItem[] { - const items: IMenuItem[] = []; - const observer = source.observer; - if (observer !== undefined) { - items.push({ - caption: 'Disconnect', - handler: () => { - observer.abort().catch((err: Error) => { - this.logger.error( - `Fail to abort observing (tcp listening): ${err.message}`, - ); - }); - }, - }); - } else { - items.push({ - caption: 'Connect', - handler: () => { - this.clone(source.observe).catch((err: Error) => { - this.logger.error(`Fail to repeat: ${err.message}`); - }); - }, - }); - } - return items; - } - - public openNewSessionOptions() { - session.initialize().configure(new Factory.Stream().tcp().get()); - } - - public update(sources: ObserveSource[]): Provider { - this._sources = sources.filter( - (source) => - source.observe.origin.nature() instanceof $.Origin.Stream.Stream.TCP.Configuration, - ); - return this; - } - - public sources(): ObserveSource[] { - return this._sources; - } - - public getPanels(): { - list(): { - name(): string; - desc(): string; - comp(): IComponentDesc; - }; - nocontent(): { - name(): string | undefined; - desc(): string | undefined; - comp(): IComponentDesc | undefined; - }; - } { - return { - list: (): { - name(): string; - desc(): string; - comp(): IComponentDesc; - } => { - return { - name: (): string => { - return `TCP Connections`; - }, - desc: (): string => { - return this._sources.length > 0 ? this._sources.length.toString() : ''; - }, - comp: (): IComponentDesc => { - return { - factory: List, - inputs: { - provider: this, - }, - }; - }, - }; - }, - nocontent: (): { - name(): string | undefined; - desc(): string | undefined; - comp(): IComponentDesc | undefined; - } => { - return { - name: (): string | undefined => { - return undefined; - }, - desc: (): string | undefined => { - return undefined; - }, - comp: (): IComponentDesc | undefined => { - return undefined; - }, - }; - }, - }; - } -} diff --git a/application/client/src/app/service/session/dependencies/observing/implementations/udp.ts b/application/client/src/app/service/session/dependencies/observing/implementations/udp.ts deleted file mode 100644 index 86e974ce2..000000000 --- a/application/client/src/app/service/session/dependencies/observing/implementations/udp.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { Provider as Base } from '../provider'; -import { ObserveSource } from '@service/session/dependencies/observing/source'; -import { IComponentDesc } from '@elements/containers/dynamic/component'; -import { List } from '@ui/views/sidebar/observe/lists/udp/component'; -import { IMenuItem } from '@ui/service/contextmenu'; -import { session } from '@service/session'; - -import * as $ from '@platform/types/observe'; -import * as Factory from '@platform/types/observe/factory'; - -export class Provider extends Base { - private _sources: ObserveSource[] = []; - - public contextMenu(source: ObserveSource): IMenuItem[] { - const items: IMenuItem[] = []; - const observer = source.observer; - if (observer !== undefined) { - items.push({ - caption: 'Disconnect', - handler: () => { - observer.abort().catch((err: Error) => { - this.logger.error( - `Fail to abort observing (udp listening): ${err.message}`, - ); - }); - }, - }); - } else { - items.push({ - caption: 'Connect', - handler: () => { - this.clone(source.observe).catch((err: Error) => { - this.logger.error(`Fail to repeat: ${err.message}`); - }); - }, - }); - } - return items; - } - - public openNewSessionOptions() { - session.initialize().configure(new Factory.Stream().udp().get()); - } - - public update(sources: ObserveSource[]): Provider { - this._sources = sources.filter( - (source) => - source.observe.origin.nature() instanceof $.Origin.Stream.Stream.UDP.Configuration, - ); - return this; - } - - public sources(): ObserveSource[] { - return this._sources; - } - - public getPanels(): { - list(): { - name(): string; - desc(): string; - comp(): IComponentDesc; - }; - nocontent(): { - name(): string | undefined; - desc(): string | undefined; - comp(): IComponentDesc | undefined; - }; - } { - return { - list: (): { - name(): string; - desc(): string; - comp(): IComponentDesc; - } => { - return { - name: (): string => { - return `UPD Connections`; - }, - desc: (): string => { - return this._sources.length > 0 ? this._sources.length.toString() : ''; - }, - comp: (): IComponentDesc => { - return { - factory: List, - inputs: { - provider: this, - }, - }; - }, - }; - }, - nocontent: (): { - name(): string | undefined; - desc(): string | undefined; - comp(): IComponentDesc | undefined; - } => { - return { - name: (): string | undefined => { - return undefined; - }, - desc: (): string | undefined => { - return undefined; - }, - comp: (): IComponentDesc | undefined => { - return undefined; - }, - }; - }, - }; - } -} diff --git a/application/client/src/app/service/session/dependencies/observing/operation.ts b/application/client/src/app/service/session/dependencies/observing/operation.ts index 2c7089c9d..edf0a3bb1 100644 --- a/application/client/src/app/service/session/dependencies/observing/operation.ts +++ b/application/client/src/app/service/session/dependencies/observing/operation.ts @@ -1,33 +1,126 @@ -import { Observe } from '@platform/types/observe'; import { SdeRequest, SdeResponse } from '@platform/types/sde'; +import { SessionDescriptor, SourceDefinition } from '@platform/types/bindings'; +import { SessionOrigin } from '@service/session/origin'; +import { Subject } from '@platform/env/subscription'; -import * as $ from '@platform/types/observe'; +type SdeAPIFunc = (msg: SdeRequest) => Promise; +type RestartingAPIFunc = (setup: SessionOrigin) => Promise; +type CancelAPIFunc = () => Promise; + +export enum State { + Started = 'started', + Running = 'running', + Done = 'finished', + Aborted = 'aborted', +} export class ObserveOperation { - private _sdeTasksCount: number = 0; + protected _sdeTasksCount: number = 0; + + protected descriptor: SessionDescriptor | undefined; + protected sde: SdeAPIFunc | undefined; + protected restarting: RestartingAPIFunc | undefined; + protected cancel: CancelAPIFunc | undefined; + protected origin: SessionOrigin | undefined; + protected sources: Map = new Map(); + + public state: State = State.Started; + public stateUpdateEvent: Subject = new Subject(); + + constructor(public readonly uuid: string) {} + + public bind( + origin: SessionOrigin, + sde: SdeAPIFunc, + restarting: RestartingAPIFunc, + cancel: CancelAPIFunc, + ) { + this.origin = origin; + this.sde = sde; + this.restarting = restarting; + this.cancel = cancel; + this.state = State.Running; + this.stateUpdateEvent.emit(); + } + + public addSource(source: SourceDefinition): boolean { + if (this.sources.has(source.id)) { + return false; + } + this.sources.set(source.id, source); + return true; + } + + public getSourcesCount(): number { + return this.sources.size; + } + + public getSource(id: number): SourceDefinition | undefined { + return this.sources.get(id); + } + + public getFirstSourceKey(): number | undefined { + return this.sources.keys().next().value; + } - constructor( - public readonly uuid: string, - protected readonly observe: Observe, - protected readonly sde: (msg: SdeRequest) => Promise, - protected readonly restarting: (observe: Observe) => Promise, - protected readonly cancel: () => Promise, - ) {} + public destroy() { + this.stateUpdateEvent.destroy(); + } public abort(): Promise { - return this.cancel(); + if (this.state === State.Aborted || this.state === State.Done) { + return Promise.resolve(); + } + if (!this.cancel) { + return Promise.reject( + new Error(`"abort" cannot be called, because ObserveOperation isn't bound`), + ); + } + return this.cancel().finally(() => { + this.state = State.Aborted; + this.stateUpdateEvent.emit(); + }); } public restart(): Promise { - return this.restarting(this.observe); + if (!this.restarting || !this.origin) { + return Promise.reject( + new Error(`"restart" cannot be called, because ObserveOperation isn't bound`), + ); + } + return this.restarting(this.origin); } - public asObserve(): Observe { - return this.observe; + public finish() { + this.state = State.Done; + this.stateUpdateEvent.emit(); } - public asOrigin(): $.Origin.Configuration { - return this.observe.origin; + public isRunning(): boolean { + return this.state === State.Running; + } + + public isStopped(): boolean { + return this.state === State.Done || this.state == State.Aborted; + } + + public isSame(operation: ObserveOperation): boolean { + return this.uuid === operation.uuid; + } + + public getOrigin(): SessionOrigin { + if (!this.origin) { + throw new Error(`Operation ${this.uuid} isn't bound yet.`); + } + return this.origin; + } + + public setDescriptor(descriptor: SessionDescriptor) { + this.descriptor = descriptor; + } + + public getDescriptor(): SessionDescriptor | undefined { + return this.descriptor; } public send(): { @@ -35,14 +128,27 @@ export class ObserveOperation { bytes(data: number[]): Promise; } { const send = (request: SdeRequest): Promise => { + if (!this.sde) { + return Promise.reject( + new Error(`"sde" cannot be called, because ObserveOperation isn't bound`), + ); + } this._sdeTasksCount += 1; return this.sde(request).finally(() => { this._sdeTasksCount -= 1; }); }; return { - text: (data: string): Promise => { - if (!this.observe.origin.isSdeSupported()) { + text: async (data: string): Promise => { + if (!this.origin) { + return Promise.reject( + new Error( + `"sde::send::text" cannot be called, because ObserveOperation isn't bound`, + ), + ); + } + const support = await this.origin.isSdeSupported(); + if (!support) { return Promise.reject( new Error(`Observed origin doesn't support SDE protocol`), ); @@ -51,8 +157,16 @@ export class ObserveOperation { WriteText: `${data}\n\r`, }); }, - bytes: (data: number[]): Promise => { - if (!this.observe.origin.isSdeSupported()) { + bytes: async (data: number[]): Promise => { + if (!this.origin) { + return Promise.reject( + new Error( + `"sde::send::bytes" cannot be called, because ObserveOperation isn't bound`, + ), + ); + } + const support = await this.origin.isSdeSupported(); + if (!support) { return Promise.reject( new Error(`Observed origin doesn't support SDE protocol`), ); diff --git a/application/client/src/app/service/session/dependencies/observing/provider.ts b/application/client/src/app/service/session/dependencies/observing/provider.ts index fb9a4f6e8..6f9f0dfc0 100644 --- a/application/client/src/app/service/session/dependencies/observing/provider.ts +++ b/application/client/src/app/service/session/dependencies/observing/provider.ts @@ -1,7 +1,6 @@ import { IComponentDesc } from '@elements/containers/dynamic/component'; import { Logger } from '@platform/log'; import { Session } from '@service/session/session'; -import { ObserveSource } from '@service/session/dependencies/observing/source'; import { unique } from '@platform/env/sequence'; import { Subject, Subjects } from '@platform/env/subscription'; import { IMenuItem } from '@ui/service/contextmenu'; @@ -9,22 +8,24 @@ import { Observe } from '@platform/types/observe'; import { session } from '@service/session'; import { components } from '@env/decorators/initial'; import { popup, Vertical, Horizontal } from '@ui/service/popup'; +import { ObserveOperation } from './operation'; import * as $ from '@platform/types/observe'; +import { SessionOrigin } from '@service/session/origin'; export interface ProviderConstructor { new (session: Session, logger: Logger): Provider; } export abstract class Provider { - static overwrite(src: ObserveSource[], dest: ObserveSource[]) { - src.filter((src) => src.observer === undefined).forEach((src) => { - const index = dest.findIndex((s) => s.observe.uuid === src.observe.uuid); - if (index !== -1) { - dest.splice(index, 1, src); - } - }); - } + // static overwrite(src: ObserveOperation[], dest: ObserveOperation[]) { + // src.filter((src) => !src.isRunning()).forEach((src) => { + // const index = dest.findIndex((s) => s.uuid === src.uuid); + // if (index !== -1) { + // dest.splice(index, 1, src); + // } + // }); + // } public readonly session: Session; public readonly logger: Logger; public readonly subjects: Subjects<{ @@ -55,25 +56,25 @@ export abstract class Provider { this.subjects.destroy(); } - public last(): Observe | undefined { - const sources = this.sources(); - if (sources.length === 0) { + public getLastOperation(): ObserveOperation | undefined { + const operations = this.operations(); + if (operations.length === 0) { return undefined; } - const observe = sources[sources.length - 1]; - return observe.observe; + const operation = operations[operations.length - 1]; + return operation; } public recent() { - const observe = this.last(); - if (observe === undefined) { + const operation = this.getLastOperation(); + if (operation === undefined) { return; } popup.open({ component: { factory: components.get('app-navigator'), inputs: { - observe, + operation, }, }, position: { @@ -90,27 +91,27 @@ export abstract class Provider { if (!(observe.origin.instance instanceof $.Origin.Stream.Configuration)) { return Promise.reject(new Error(`Only Origin.Stream can be repeated`)); } - const last = this.last(); - if (last !== undefined) { - observe.parser.change(last.parser.instance); - } - return this.session.stream.observe().start(observe.clone()); + const last = this.getLastOperation(); + // if (last !== undefined) { + // observe.parser.change(last.parser.instance); + // } + // return this.session.stream.observe().start(observe.clone()); + return Promise.reject(new Error(`Not implemented`)); } - public openAsNewOrigin(observe: Observe): Promise { - const last = this.last(); - if (last !== undefined) { - observe.parser.change(last.parser.instance); - } else { - return Promise.reject(new Error(`No data about current parser`)); - } - return this.session.stream.observe().start(observe.clone()); + public openAsNewOrigin(origin: SessionOrigin): Promise { + // const last = this.last(); + // if (last !== undefined) { + // observe.parser.change(last.parser.instance); + // } else { + // return Promise.reject(new Error(`No data about current parser`)); + // } + // return this.session.stream.observe().start(observe.clone()); + return Promise.reject(new Error(`Not implemented`)); } - public openAsNew(source: ObserveSource | Observe): Promise { - return session - .initialize() - .configure(source instanceof ObserveSource ? source.observe : source); + public openAsNew(origin: SessionOrigin): Promise { + return session.initialize().configure(origin); } public setPanels(): Provider { @@ -130,14 +131,14 @@ export abstract class Provider { } public isEmpty(): boolean { - return this.sources().length === 0; + return this.operations().length === 0; } - public abstract contextMenu(source: ObserveSource): IMenuItem[]; + public abstract contextMenu(operations: ObserveOperation): IMenuItem[]; - public abstract update(sources: ObserveSource[]): Provider; + public abstract update(operations: ObserveOperation[]): Provider; - public abstract sources(): ObserveSource[]; + public abstract operations(): ObserveOperation[]; public abstract getPanels(): { list(): { @@ -157,6 +158,6 @@ export abstract class Provider { } public count(): number { - return this.sources().length; + return this.operations().length; } } diff --git a/application/client/src/app/service/session/dependencies/observing/providers.ts b/application/client/src/app/service/session/dependencies/observing/providers.ts index 894edcee5..3d0e3b74c 100644 --- a/application/client/src/app/service/session/dependencies/observing/providers.ts +++ b/application/client/src/app/service/session/dependencies/observing/providers.ts @@ -3,7 +3,6 @@ import { Provider, ProviderConstructor } from './provider'; import { Session } from '@service/session/session'; import { Subscriber } from '@platform/env/subscription'; import { Mutable } from '@platform/types/unity/mutable'; -import { PROVIDERS } from './implementations'; import { cutUuid } from '@log/index'; @SetupLogger() @@ -13,24 +12,24 @@ export class Providers extends Subscriber { public readonly list: Map = new Map(); public init(session: Session) { - this.setLoggerName(`Providers: ${cutUuid(session.uuid())}`); - (this as Mutable).session = session; - PROVIDERS.forEach((providerConstructor: ProviderConstructor) => { - const provider = new providerConstructor(session, this.log()); - this.list.set(provider.uuid, provider.setPanels()); - }); - this.register( - session.stream.subjects.get().sources.subscribe(() => { - this.update(); - }), - session.stream.subjects.get().finished.subscribe(() => { - this.update(); - }), - session.stream.sde.subjects.get().selected.subscribe(() => { - this.update(); - }), - ); - this.update(); + // this.setLoggerName(`Providers: ${cutUuid(session.uuid())}`); + // (this as Mutable).session = session; + // PROVIDERS.forEach((providerConstructor: ProviderConstructor) => { + // const provider = new providerConstructor(session, this.log()); + // this.list.set(provider.uuid, provider.setPanels()); + // }); + // this.register( + // session.stream.subjects.get().sources.subscribe(() => { + // this.update(); + // }), + // session.stream.subjects.get().finished.subscribe(() => { + // this.update(); + // }), + // session.stream.sde.subjects.get().selected.subscribe(() => { + // this.update(); + // }), + // ); + // this.update(); } public destroy() { @@ -60,9 +59,9 @@ export class Providers extends Subscriber { } protected update() { - const sources = this.session.stream.observe().sources(); + const operations = this.session.stream.observe().operations(); this.list.forEach((provider, _uuid) => { - provider.update(sources).subjects.get().updated.emit(); + provider.update(operations).subjects.get().updated.emit(); }); } } diff --git a/application/client/src/app/service/session/dependencies/observing/sde.ts b/application/client/src/app/service/session/dependencies/observing/sde.ts index 031afb7fb..3787c62f2 100644 --- a/application/client/src/app/service/session/dependencies/observing/sde.ts +++ b/application/client/src/app/service/session/dependencies/observing/sde.ts @@ -59,8 +59,25 @@ export class Sde { }); } - public overwrite(running: Map): void { - this.operations = Array.from(running.values()).filter((s) => s.asOrigin().isSdeSupported()); + public async overwrite(all: Map): Promise { + const running = Array.from(all.values()).filter((s) => s.isRunning()); + this.operations = []; + for (let operation of running) { + const sde: boolean = await operation + .getOrigin() + .isSdeSupported() + .catch((err: Error) => { + this.log().error( + `Fail to get SDE support info for ${operation.getOrigin().getTitle()} (${ + operation.uuid + }): ${err.message}`, + ); + return false; + }); + if (sde) { + this.operations.push(operation); + } + } this.subjects.get().updated.emit(); if (this.selected !== undefined) { const target = this.selected.uuid; @@ -110,7 +127,7 @@ export class Sde { is: (uuid: string): boolean => { return this.selected === undefined ? false - : this.selected.uuid === uuid || this.selected.asObserve().uuid === uuid; + : this.selected.uuid === uuid || this.selected.uuid === uuid; }, first: (): void => { if (this.operations.length === 0) { @@ -134,9 +151,7 @@ export class Sde { if (this.selected !== undefined && this.selected.uuid === uuid) { return false; } - const candidate = this.operations.find( - (o) => o.uuid === uuid || o.asObserve().uuid === uuid, - ); + const candidate = this.operations.find((o) => o.uuid === uuid || o.uuid === uuid); if (candidate === undefined) { return false; } diff --git a/application/client/src/app/service/session/dependencies/observing/source.ts b/application/client/src/app/service/session/dependencies/observing/source.ts deleted file mode 100644 index 55ccd622e..000000000 --- a/application/client/src/app/service/session/dependencies/observing/source.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ObserveOperation } from './operation'; -import { Observe } from '@platform/types/observe'; -import { Mutable } from '@platform/types/unity/mutable'; - -export class ObserveSource { - public readonly observe: Observe; - public readonly observer: ObserveOperation | undefined; - public readonly child: boolean = false; - - constructor(observe: Observe, observer?: ObserveOperation) { - this.observe = observe; - this.observer = observer; - } - - public uuid(): string { - return this.observe.uuid; - } - - public isSame(source: ObserveSource): boolean { - return this.uuid() === source.uuid(); - } - - public asChild(): ObserveSource { - (this as Mutable).child = true; - return this; - } -} diff --git a/application/client/src/app/service/session/dependencies/stream.ts b/application/client/src/app/service/session/dependencies/stream.ts index 0408bb0ea..b4ef741cb 100644 --- a/application/client/src/app/service/session/dependencies/stream.ts +++ b/application/client/src/app/service/session/dependencies/stream.ts @@ -3,18 +3,18 @@ import { Subscriber, Subjects, Subject } from '@platform/env/subscription'; import { Range, IRange } from '@platform/types/range'; import { cutUuid } from '@log/index'; import { Rank } from './rank'; -import { GrabbedElement } from '@platform/types/bindings/miscellaneous'; -import { Observe } from '@platform/types/observe'; +import { GrabbedElement, SourceDefinition } from '@platform/types/bindings/miscellaneous'; import { ObserveOperation } from './observing/operation'; -import { ObserveSource } from './observing/source'; import { Info } from './info'; import { lockers } from '@ui/service/lockers'; import { Sde } from './observing/sde'; import { TextExportOptions } from '@platform/types/exporting'; +import { SessionDescriptor } from '@platform/types/bindings'; +import { SessionOrigin } from '../origin'; +import { recent } from '@service/recent'; import * as Requests from '@platform/ipc/request'; import * as Events from '@platform/ipc/event'; -import * as $ from '@platform/types/observe'; export { ObserveOperation }; @@ -24,36 +24,33 @@ export class Stream extends Subscriber { // Stream is updated (new rows came) updated: Subject; // New observe operation is started - started: Subject; + started: Subject; // Observe operation for source is finished - finished: Subject; + finished: Subject; // List of sources (observed operations has been changed) sources: Subject; // Session rank is changed rank: Subject; // Grabber is inited readable: Subject; + // New session descriptor has been gotten (describe a observe operation in human readable way) + descriptor: Subject; }> = new Subjects({ updated: new Subject(), - started: new Subject(), - finished: new Subject(), + started: new Subject(), + finished: new Subject(), sources: new Subject(), rank: new Subject(), readable: new Subject(), + descriptor: new Subject(), }); private _len: number = 0; private _uuid!: string; private _info!: Info; - public readonly observed: { - running: Map; - done: Map; - map: Map; - } = { - running: new Map(), - done: new Map(), - map: new Map(), - }; + public readonly operations: Map = new Map(); + // Original (initial) session origin + public origin: SessionOrigin | undefined; public readonly rank: Rank = new Rank(); public sde!: Sde; @@ -76,42 +73,25 @@ export class Stream extends Subscriber { }), ); this.register( - Events.IpcEvent.subscribe(Events.Observe.Started.Event, (event) => { + Events.IpcEvent.subscribe(Events.Stream.SessionDescriptor.Event, (event) => { if (event.session !== this._uuid) { return; } - const observe = Observe.from(event.source); - if (observe instanceof Error) { - this.log().error(`Fail to parse Observe: ${observe.message}`); + this.subjects.get().descriptor.emit(event.descriptor); + const operation = this.operations.get(event.operation); + if (!operation) { + this.log().error( + `Event "Stream.SessionDescriptor" emmited for operation "${event.operation}", but there no started operation with same uuid.`, + ); return; } - this.observed.running.set( - event.operation, - new ObserveOperation( - event.operation, - observe, - this.sde.send.bind(this.sde, event.operation), - this.observe().restart.bind(this, event.operation), - this.observe().abort.bind(this, event.operation), - ), - ); - this.observe() - .descriptions.request() - .then((sources) => { - let updated = false; - sources.forEach((source) => { - if (!this.observed.map.has(source.id)) { - this.observed.map.set(source.id, source); - updated = true; - } - }); - updated && this.subjects.get().sources.emit(); - }) - .catch((err: Error) => { - this.log().error(`Fail get sources description: ${err.message}`); - }); - this.sde.overwrite(this.observed.running); - this.subjects.get().started.emit(observe); + operation.setDescriptor(event.descriptor); + }), + Events.IpcEvent.subscribe(Events.Observe.Started.Event, (event) => { + if (event.session !== this._uuid) { + return; + } + this.operations.set(event.operation, new ObserveOperation(event.operation)); }), ); this.register( @@ -119,14 +99,16 @@ export class Stream extends Subscriber { if (event.session !== this._uuid) { return; } - const stored = this.observed.running.get(event.operation); - if (stored === undefined) { + const operation = this.operations.get(event.operation); + if (!operation) { + this.log().error( + `Event "Observe.Finished" emmited for operation "${event.operation}", but there no started operation with same uuid.`, + ); return; } - this.observed.done.set(event.operation, stored.asObserve()); - this.observed.running.delete(event.operation); - this.sde.overwrite(this.observed.running); - this.subjects.get().finished.emit(stored.asObserve()); + operation.finish(); + this.sde.overwrite(this.operations); + this.subjects.get().finished.emit(operation); }), ); } @@ -135,6 +117,16 @@ export class Stream extends Subscriber { this.unsubscribe(); this.subjects.destroy(); this.sde.destroy(); + this.operations.forEach((operation) => { + operation.abort().catch((err: Error) => { + this.log().warn(`Fail to abort operation: ${err.message}`); + }); + operation.destroy(); + }); + } + + public getOrigin(): SessionOrigin | undefined { + return this.origin; } public len(): number { @@ -142,39 +134,102 @@ export class Stream extends Subscriber { } public observe(): { - start(observe: Observe): Promise; + start(options: SessionOrigin): Promise; abort(uuid: string): Promise; - restart(uuid: string, source: Observe): Promise; - list(): Promise>; - sources(): ObserveSource[]; + restart(uuid: string, options: SessionOrigin): Promise; + list(): Promise>; + operations(): ObserveOperation[]; isFileSource(): boolean; getSourceFileName(): string | undefined; descriptions: { - get(id: number): $.Types.ISourceLink | undefined; - id(alias: string): number | undefined; - request(): Promise<$.Types.ISourceLink[]>; + get(id: number): SourceDefinition | undefined; + id(uuid: string): number | undefined; + request(): Promise; count(): number; }; } { return { - start: (observe: Observe): Promise => { + start: (origin: SessionOrigin): Promise => { return Requests.IpcRequest.send( Requests.Observe.Start.Response, new Requests.Observe.Start.Request({ session: this._uuid, - observe: observe.sterilized(), + options: origin.getSessionSetup(), }), ) .then((response) => { if (typeof response.error === 'string' && response.error !== '') { return Promise.reject(new Error(response.error)); } - this._info.fromObserveInfo(observe); + if (typeof response.uuid !== 'string' || response.uuid.trim() === '') { + return Promise.reject( + new Error(`Invalid session start operation UUID`), + ); + } + const operationUuid = response.uuid; + if (!operationUuid) { + return Promise.reject( + new Error( + `No operation UUID has been recieved: ${JSON.stringify( + response, + )}`, + ), + ); + } + const operation = this.operations.get(operationUuid); + if (!operation) { + return Promise.reject( + new Error( + `Operation ${operationUuid} didn't sent Started event: ${JSON.stringify( + response, + )}`, + ), + ); + } + operation.bind( + origin, + this.sde.send.bind(this.sde, operationUuid), + this.observe().restart.bind(this, operationUuid), + this.observe().abort.bind(this, operationUuid), + ); + this.observe() + .descriptions.request() + .then((sources) => { + let updated = false; + sources + .filter((source) => source.uuid === operationUuid) + .forEach((source) => { + const added = operation.addSource(source); + updated = updated ? updated : added; + }); + updated && this.subjects.get().sources.emit(); + }) + .catch((err: Error) => { + this.log().error(`Fail get sources description: ${err.message}`); + }); + this.sde.overwrite(this.operations); + this.subjects.get().started.emit(operation); + if (this.operations.size === 1) { + // Only if it's the first operation, save as recent + recent.add(operation); + } + if (!this.origin) { + this.origin = origin; + } return response.session; }) .finally(lockers.progress(`Creating session...`)); }, abort: (uuid: string): Promise => { + const operation = this.operations.get(uuid); + if (!operation) { + return Promise.reject( + new Error(`Operation ${uuid} doesn't exist. Cannot abort`), + ); + } + if (operation.isStopped()) { + return Promise.resolve(); + } return new Promise((resolve, reject) => { Requests.IpcRequest.send( Requests.Observe.Abort.Response, @@ -196,14 +251,20 @@ export class Stream extends Subscriber { }); }); }, - restart: (uuid: string, observe: Observe): Promise => { + restart: (uuid: string, options: SessionOrigin): Promise => { + const operation = this.operations.get(uuid); + if (!operation) { + return Promise.reject( + new Error(`Operation ${uuid} doesn't exist. Cannot restart`), + ); + } return this.observe() .abort(uuid) .then(() => { - return this.observe().start(observe); + return this.observe().start(options); }); }, - list: (): Promise> => { + list: (): Promise> => { return new Promise((resolve) => { Requests.IpcRequest.send( Requests.Observe.List.Response, @@ -212,16 +273,18 @@ export class Stream extends Subscriber { }), ) .then((response: Requests.Observe.List.Response) => { - const sources: Map = new Map(); - Object.keys(response.sources).forEach((uuid: string) => { - const source = Observe.from(response.sources[uuid]); - if (source instanceof Error) { - this.log().error(`Fail to parse Observe: ${source.message}`); - return; + const operations: Map = new Map(); + response.operations.forEach((uuid: string) => { + const operation = this.operations.get(uuid); + if (!operation) { + this.log().error( + `Fail to find operation ${uuid} in local scope`, + ); + } else { + operations.set(uuid, operation); } - sources.set(uuid, source); }); - resolve(sources); + resolve(operations); }) .catch((error: Error) => { this.log().error( @@ -230,45 +293,38 @@ export class Stream extends Subscriber { }); }); }, - sources: (): ObserveSource[] => { - const sources: ObserveSource[] = []; - Array.from(this.observed.running.values()).forEach((observed: ObserveOperation) => { - sources.push(new ObserveSource(observed.asObserve(), observed)); - }); - Array.from(this.observed.done.values()).forEach((source: Observe) => { - sources.push(new ObserveSource(source)); - }); - return sources; + operations: (): ObserveOperation[] => { + return Array.from(this.operations.values()); }, isFileSource: (): boolean => { - const sources = this.observe().sources(); + const sources = this.observe().operations(); if (sources.length !== 1) { return false; } - return sources[0].observe.origin.files() !== undefined; + return sources[0].getOrigin().getFirstFilename() !== undefined; }, getSourceFileName: (): string | undefined => { - const sources = this.observe().sources(); + const sources = this.observe().operations(); if (sources.length !== 1) { return undefined; } - const files = sources[0].observe.origin.files(); - if (files === undefined || (files instanceof Array && files.length === 0)) { - return undefined; - } - return files instanceof Array ? files[0] : files; + return sources[0].getOrigin().getFirstFilename(); }, descriptions: { - get: (id: number): $.Types.ISourceLink | undefined => { - return this.observed.map.get(id); + get: (id: number): SourceDefinition | undefined => { + const operation = Array.from(this.operations.values()).find((operation) => { + return operation.getSource(id); + }); + return operation ? operation.getSource(id) : undefined; }, - id: (alias: string): number | undefined => { - const link = Array.from(this.observed.map.values()).find( - (s) => s.alias === alias, - ); - return link !== undefined ? link.id : undefined; + id: (uuid: string): number | undefined => { + // TODO: + // `uuid` - is an uuid of observe operation, but if it's Files (aka concat) + // we would have multiple sources (files) for a one operation + const operation = this.operations.get(uuid); + return operation ? operation.getFirstSourceKey() : undefined; }, - request: (): Promise<$.Types.ISourceLink[]> => { + request: (): Promise => { return new Promise((resolve, reject) => { Requests.IpcRequest.send( Requests.Observe.SourcesDefinitionsList.Response, @@ -283,7 +339,9 @@ export class Stream extends Subscriber { }); }, count: (): number => { - return this.observed.map.size; + return Array.from(this.operations.values()) + .map((operation) => operation.getSourcesCount()) + .reduce((sum, a) => sum + a, 0); }, }, }; diff --git a/application/client/src/app/service/session/dependencies/teamwork.ts b/application/client/src/app/service/session/dependencies/teamwork.ts index edcfb59bc..373ac71c5 100644 --- a/application/client/src/app/service/session/dependencies/teamwork.ts +++ b/application/client/src/app/service/session/dependencies/teamwork.ts @@ -1,7 +1,6 @@ import { SetupLogger, LoggerInterface } from '@platform/entity/logger'; import { Subscriber, Subjects, Subject } from '@platform/env/subscription'; import { cutUuid } from '@log/index'; -import { Observe } from '@platform/types/observe'; import { GitHubRepo } from '@platform/types/github'; import { FileMetaDataDefinition, FileMetaData } from '@platform/types/github/filemetadata'; import { Session } from '@service/session'; @@ -17,6 +16,7 @@ import * as utils from '@platform/log/utils'; import * as Requests from '@platform/ipc/request'; import * as Events from '@platform/ipc/event'; import * as moment from 'moment'; +import { ObserveOperation } from './stream'; export interface GitHubError { time: string; @@ -386,11 +386,11 @@ export class TeamWork extends Subscriber { ); }, ), - this.session.stream.subjects.get().started.subscribe((observe: Observe) => { + this.session.stream.subjects.get().started.subscribe((operation: ObserveOperation) => { if (this.checksum === null) { return; } - FileDesc.fromDataSource(observe) + FileDesc.fromDataSource(operation) .then((desc) => { if (desc === undefined) { this.checksum = null; diff --git a/application/client/src/app/service/session/origin.ts b/application/client/src/app/service/session/origin.ts new file mode 100644 index 000000000..18cf955c1 --- /dev/null +++ b/application/client/src/app/service/session/origin.ts @@ -0,0 +1,206 @@ +import { + SessionAction, + Ident, + SessionSetup, + ComponentOptions, + OutputRender, + SessionDescriptor, +} from '@platform/types/bindings'; +import { Render } from '@schema/render'; +import { ColumnsRender } from '@schema/render/columns'; +import { TextRender } from '@schema/render/text'; +import { components } from '@service/components'; + +export class SessionComponents { + public parser: Ident | undefined; + public source: Ident | undefined; + public setParser(ident: Ident): SessionComponents { + this.parser = ident; + return this; + } + public setSource(ident: Ident): SessionComponents { + this.source = ident; + return this; + } +} + +export class ComponentsOptions { + public parser: ComponentOptions | undefined; + public source: ComponentOptions | undefined; + public setParser(options: ComponentOptions): ComponentsOptions { + this.parser = options; + return this; + } + public setSource(options: ComponentOptions): ComponentsOptions { + this.source = options; + return this; + } +} + +export class SessionOrigin { + static fromSessionSetup(setup: SessionSetup, descriptor: SessionDescriptor): SessionOrigin { + return new SessionOrigin( + setup.origin, + new SessionComponents().setSource(descriptor.source).setParser(descriptor.parser), + new ComponentsOptions().setParser(setup.parser).setSource(setup.source), + ); + } + + static file(path: string): SessionOrigin { + return new SessionOrigin({ File: path }, undefined); + } + static files(paths: string[]): SessionOrigin { + return new SessionOrigin({ Files: paths }, undefined); + } + static source(): SessionOrigin { + return new SessionOrigin('Source', undefined); + } + + public readonly options: ComponentsOptions; + + constructor( + public readonly origin: SessionAction, + public components: SessionComponents | undefined, + options?: ComponentsOptions, + ) { + this.options = options ? options : new ComponentsOptions(); + } + + public setComponents(components: SessionComponents) { + this.components = components; + } + + public getSessionSetup(): SessionSetup { + if (!this.options.parser) { + throw new Error(`Parser isn't defined`); + } + if (!this.options.source) { + throw new Error(`Source isn't defined`); + } + return { + origin: this.origin, + source: this.options.source, + parser: this.options.parser, + }; + } + + public getDef(): SessionAction { + return this.origin; + } + + public getRender(): Promise> { + const parser = this.options.parser; + if (!parser) { + return Promise.reject(new Error(`No parsed defined`)); + } + return new Promise((resolve, reject) => { + components + .getOutputRender(parser.uuid) + .then((render: OutputRender | null | undefined) => { + if (!render) { + reject(new Error(`No output render for parser ${parser.uuid}`)); + return; + } + if (render === 'PlaitText') { + return resolve(new TextRender()); + } else if (typeof render === 'object') { + if ((render as { Columns: Array<[string, number]> }).Columns) { + const schema = (render as { Columns: Array<[string, number]> }).Columns; + return resolve( + new ColumnsRender( + schema.map((data: [string, number]) => data[0]), + schema.map((data: [string, number]) => data[1]), + ), + ); + } + } + }) + .catch(reject); + }); + } + + public getTitle(): string { + if (this.origin === 'Source') { + // TODO: Check idents + return `Source`; + } else if ((this.origin as { File: string }).File) { + return (this.origin as { File: string }).File; + } else if ((this.origin as { Files: string[] }).Files) { + return `${(this.origin as { Files: string[] }).Files.length} files`; + } else { + return `unknown`; + } + } + + public getDescription(): { title: string; desctiption: string | undefined } { + if (this.origin === 'Source') { + // TODO: Check idents + return { + title: 'Custom Source', + desctiption: `Data comes from selected source provider`, + }; + } else if ((this.origin as { File: string }).File) { + return { title: `Selected File`, desctiption: (this.origin as { File: string }).File }; + } else if ((this.origin as { Files: string[] }).Files) { + return { + title: `Collection of Files`, + desctiption: `${(this.origin as { Files: string[] }).Files.length} files`, + }; + } else { + return { title: 'Unknown', desctiption: undefined }; + } + } + + public isSdeSupported(): Promise { + if (!this.options || !this.options.source) { + return Promise.reject(new Error(`Source isn't defined`)); + } + return components.isSdeSupported(this.options.source.uuid, this.origin); + } + + public isFile(): boolean { + return typeof (this.origin as { File: string }).File === 'string'; + } + + public isFiles(): boolean { + return (this.origin as { Files: string[] }).Files instanceof Array; + } + + public isStream(): boolean { + return typeof this.origin === 'string' && this.origin === 'Source'; + } + + public isSameAction(other: SessionAction): boolean { + if ((this.origin as { File: string }).File && (other as { File: string }).File) { + return true; + } else if ( + (this.origin as { Files: string[] }).Files && + (other as { Files: string[] }).Files + ) { + return true; + } else if (this.origin === 'Source' && other === 'Source') { + return true; + } else { + return false; + } + } + + public getFirstFilename(): string | undefined { + if ((this.origin as { File: string }).File) { + return (this.origin as { File: string }).File; + } else if ((this.origin as { Files: string[] }).Files) { + const files = (this.origin as { Files: string[] }).Files; + return files.length > 0 ? files[0] : undefined; + } else { + return undefined; + } + } + public getFiles(): string[] | undefined { + if ((this.origin as { Files: string[] }).Files) { + const files = (this.origin as { Files: string[] }).Files; + return files instanceof Array ? files : undefined; + } else { + return undefined; + } + } +} diff --git a/application/client/src/app/service/session/session.ts b/application/client/src/app/service/session/session.ts index 3a865e181..df8ce0935 100644 --- a/application/client/src/app/service/session/session.ts +++ b/application/client/src/app/service/session/session.ts @@ -14,10 +14,8 @@ import { Bookmarks } from './dependencies/bookmarks'; import { Comments } from './dependencies/comments'; import { Exporter } from './dependencies/exporter'; import { IRange, fromIndexes } from '@platform/types/range'; -import { Providers } from './dependencies/observing/providers'; import { Attachments } from './dependencies/attachments'; import { Info } from './dependencies/info'; -import { session } from '@service/session'; import { Highlights } from './dependencies/search/highlights'; import { TeamWork } from './dependencies/teamwork'; import { Cli } from './dependencies/cli'; @@ -27,13 +25,10 @@ import { DisabledRequest } from './dependencies/search/disabled/request'; import { StoredEntity } from './dependencies/search/store'; import { Notification, notifications } from '@ui/service/notifications'; import { error } from '@platform/log/utils'; +import { SessionDescriptor } from '@platform/types/bindings'; import * as ids from '@schema/ids'; import * as Requests from '@platform/ipc/request'; -import * as Origins from '@platform/types/observe/origin/index'; -import * as Factory from '@platform/types/observe/factory'; -import * as Parsers from '@platform/types/observe/parser/index'; -import * as Types from '@platform/types/observe/types/index'; export { Stream }; @@ -56,7 +51,6 @@ export class Session extends Base { public readonly highlights: Highlights = new Highlights(); public readonly exporter: Exporter = new Exporter(); public readonly render: Render; - public readonly observed: Providers = new Providers(); public readonly attachments: Attachments = new Attachments(); public readonly info: Info = new Info(); public readonly teamwork: TeamWork = new TeamWork(); @@ -64,6 +58,7 @@ export class Session extends Base { private _uuid!: string; private _tab!: ITabAPI; + private _descriptor: SessionDescriptor | undefined; private readonly _toolbar: TabsService = new TabsService(); private readonly _sidebar: TabsService = new TabsService({ options: new TabsOptions({ direction: ETabsListDirection.left }), @@ -208,13 +203,23 @@ export class Session extends Base { this.comments.init(this); this.search.init(this); this.exporter.init(this._uuid, this.stream, this.indexed); - this.observed.init(this); this.attachments.init(this._uuid); this.charts.init(this._uuid, this.stream, this.search); this.highlights.init(this); this.teamwork.init(this); this.cli.init(this); this.inited = true; + this.register( + this.stream.subjects + .get() + .descriptor.subscribe((descriptor: SessionDescriptor) => { + if (this._descriptor) { + return; + } + this._descriptor = descriptor; + this.title().check(); + }), + ); resolve(this._uuid); }) .catch(reject); @@ -231,7 +236,6 @@ export class Session extends Base { this.comments.destroy(); this.cursor.destroy(); this.exporter.destroy(); - this.observed.destroy(); this.attachments.destroy(); this.charts.destroy(); this.info.destroy(); @@ -261,6 +265,7 @@ export class Session extends Base { public bind(tab: ITabAPI) { this._tab = tab; + this.title().check(); } public uuid(): string { @@ -352,6 +357,7 @@ export class Session extends Base { public title(): { set(title: string): Error | undefined; get(): Error | string; + check(): void; } { return { set: (title: string): Error | undefined => { @@ -360,6 +366,25 @@ export class Session extends Base { get: (): Error | string => { return this._tab.getTitle(); }, + check: () => { + if (!this._tab) { + return; + } + if (!this._descriptor) { + return; + } + this._tab.setTitle( + `${ + this._descriptor.s_desc + ? this._descriptor.s_desc + : this._descriptor.source.name + } (${ + this._descriptor.p_desc + ? this._descriptor.p_desc + : this._descriptor.parser.name + })`, + ); + }, }; } @@ -457,61 +482,63 @@ export class Session extends Base { if (filepath === undefined) { return; } - const sources = this.stream.observe().sources(); + const sources = this.stream.observe().operations(); if (sources.length === 0) { throw new Error(`Fail to find bound source`); } - const current = sources[0].observe.clone(); - const parentSearchStore = this.search.store(); - const observe = (() => { - const file = current.origin.as(Origins.File.Configuration); - const concat = current.origin.as( - Origins.Concat.Configuration, - ); - if (file !== undefined) { - file.set().filename(filepath); - return current; - } else if (concat !== undefined) { - if (concat.filetypes().length === 0) { - throw new Error(`Cannot find type of concated files`); - } - return new Factory.File().type(concat.filetypes()[0]).file(filepath).asDlt().get(); - } else { - const observe = new Factory.File() - .type( - (() => { - switch (current.parser.alias()) { - case Parsers.Protocol.Text: - case Parsers.Protocol.Plugin: - return Types.File.FileType.Text; - case Parsers.Protocol.Dlt: - case Parsers.Protocol.SomeIp: - throw new Error( - `Exporting from none-text streams to create new session aren't supported yet`, - ); - } - })(), - ) - .file(filepath) - .asText() - .get(); - observe.parser.overwrite(current.parser.configuration); - return observe; - } - })(); - return session - .initialize() - .observe(observe) - .then((uuid: string) => { - const created = session.get(uuid); - if (created === undefined) { - this.log().error(`Fail to find created session ${uuid}`); - return; - } - created.search.store().filters().overwrite(parentSearchStore.filters().get()); - created.search.store().charts().overwrite(parentSearchStore.charts().get()); - created.search.store().disabled().overwrite(parentSearchStore.disabled().get()); - }); + // const current = sources[0].observe.clone(); + // const parentSearchStore = this.search.store(); + // const observe = (() => { + // const file = current.origin.as(Origins.File.Configuration); + // const concat = current.origin.as( + // Origins.Concat.Configuration, + // ); + // if (file !== undefined) { + // file.set().filename(filepath); + // return current; + // } else if (concat !== undefined) { + // if (concat.filetypes().length === 0) { + // throw new Error(`Cannot find type of concated files`); + // } + // return new Factory.File().type(concat.filetypes()[0]).file(filepath).asDlt().get(); + // } else { + // const observe = new Factory.File() + // .type( + // (() => { + // switch (current.parser.alias()) { + // case Parsers.Protocol.Text: + // case Parsers.Protocol.Plugin: + // return Types.File.FileType.Text; + // case Parsers.Protocol.Dlt: + // case Parsers.Protocol.SomeIp: + // throw new Error( + // `Exporting from none-text streams to create new session aren't supported yet`, + // ); + // } + // })(), + // ) + // .file(filepath) + // .asText() + // .get(); + // observe.parser.overwrite(current.parser.configuration); + // return observe; + // } + // })(); + console.error(`Not implemented`); + return Promise.reject(new Error(`Not implemented`)); + // return session + // .initialize() + // .observe(observe) + // .then((uuid: string) => { + // const created = session.get(uuid); + // if (created === undefined) { + // this.log().error(`Fail to find created session ${uuid}`); + // return; + // } + // created.search.store().filters().overwrite(parentSearchStore.filters().get()); + // created.search.store().charts().overwrite(parentSearchStore.charts().get()); + // created.search.store().disabled().overwrite(parentSearchStore.disabled().get()); + // }); } } export interface Session extends LoggerInterface {} diff --git a/application/client/src/app/ui/elements/menu.attachsource/component.ts b/application/client/src/app/ui/elements/menu.attachsource/component.ts index bc60fe14e..42e9c8db2 100644 --- a/application/client/src/app/ui/elements/menu.attachsource/component.ts +++ b/application/client/src/app/ui/elements/menu.attachsource/component.ts @@ -5,12 +5,22 @@ import { ElementRef, ViewEncapsulation, ChangeDetectionStrategy, + AfterContentInit, } from '@angular/core'; -import { Session } from '@service/session'; +import { session, Session } from '@service/session'; import { Ilc, IlcInterface } from '@env/decorators/component'; import { ChangesDetector } from '@ui/env/extentions/changes'; +import { popup, Vertical, Horizontal } from '@ui/service/popup'; +import { SetupObserve } from '@tabs/setup/component'; +import { SessionOrigin } from '@service/session/origin'; +import { components } from '@service/components'; +import { Ident } from '@platform/types/bindings'; -import * as Factory from '@platform/types/observe/factory'; +interface Action { + icon: string; + title: string; + handler: () => void; +} @Component({ selector: 'app-attach-new-source-menu', @@ -21,53 +31,23 @@ import * as Factory from '@platform/types/observe/factory'; standalone: false, }) @Ilc() -export class AttachSourceMenu extends ChangesDetector { +export class AttachSourceMenu extends ChangesDetector implements AfterContentInit { @Input() session!: Session; - public actions: Array<{ icon: string; title: string; handler: () => void } | null> = [ - { - icon: 'note_add', - title: 'Attach Files', - handler: () => { - throw new Error(`Not implemented!`); - }, - }, - null, - { - icon: 'input', - title: 'Connect TCP', - handler: () => { - this.ilc() - .services.system.session.initialize() - .configure(new Factory.Stream().tcp().get(), this.session); - }, - }, - { - icon: 'input', - title: 'Connect UDP', - handler: () => { - this.ilc() - .services.system.session.initialize() - .configure(new Factory.Stream().udp().get(), this.session); - }, - }, - { - icon: 'settings_input_composite', - title: 'Connect Serial', - handler: () => { - this.ilc() - .services.system.session.initialize() - .configure(new Factory.Stream().serial().get(), this.session); - }, - }, - null, + public loading: boolean = true; + public error: string | undefined; + + protected logAndSetErr(msg: string) { + this.error = msg; + this.log().error(msg); + } + + public actions: Array = [ { - icon: 'minimize', - title: 'Command', + icon: '', + title: 'Attach New', handler: () => { - this.ilc() - .services.system.session.initialize() - .configure(new Factory.Stream().process().get(), this.session); + session.initialize().attach(this.session, undefined); }, }, ]; @@ -76,19 +56,52 @@ export class AttachSourceMenu extends ChangesDetector { super(cdRef); } - public attach(): { - disabled(): boolean; - error(): string | undefined; - } { - return { - disabled: (): boolean => { - return this.session.observed.getNewSourceError() instanceof Error; - }, - error: (): string | undefined => { - const error = this.session.observed.getNewSourceError(); - return error instanceof Error ? error.message : undefined; - }, - }; + public ngAfterContentInit(): void { + const origin = this.session.stream.getOrigin(); + if (!origin) { + this.logAndSetErr(`No origin linked to the session`); + return; + } + const parser = origin.components?.parser?.uuid; + if (!parser) { + this.logAndSetErr(`No parser linked to the origin of the session`); + return; + } + const source = origin.components?.source?.uuid; + if (!source) { + this.logAndSetErr(`No source linked to the origin of the session`); + return; + } + components + .get(origin.origin) + .sourcesForParser(parser) + .then((sources: Ident[]) => { + if (sources.length <= 1) { + return; + } + this.actions.push(null); + this.actions.push( + ...sources + .filter((src) => src.uuid !== source) + .map((src) => { + return { + icon: '', + title: src.name, + handler: () => { + session.initialize().attach(this.session, src.uuid); + }, + }; + }), + ); + this.detectChanges(); + }) + .catch((err: Error) => { + this.logAndSetErr(err.message); + }) + .finally(() => { + this.loading = false; + this.detectChanges(); + }); } } export interface AttachSourceMenu extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/menu.attachsource/module.ts b/application/client/src/app/ui/elements/menu.attachsource/module.ts index 952da46e9..6d7553fda 100644 --- a/application/client/src/app/ui/elements/menu.attachsource/module.ts +++ b/application/client/src/app/ui/elements/menu.attachsource/module.ts @@ -4,8 +4,8 @@ import { ContainersModule } from '@elements/containers/module'; import { MatButtonModule } from '@angular/material/button'; import { AttachSourceMenu } from './component'; import { MatMenuModule } from '@angular/material/menu'; -import { MatIconModule } from '@angular/material/icon'; import { MatDividerModule } from '@angular/material/divider'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; @NgModule({ imports: [ @@ -13,8 +13,8 @@ import { MatDividerModule } from '@angular/material/divider'; ContainersModule, MatButtonModule, MatMenuModule, - MatIconModule, MatDividerModule, + MatProgressSpinnerModule, ], declarations: [AttachSourceMenu], exports: [AttachSourceMenu], diff --git a/application/client/src/app/ui/elements/menu.attachsource/template.html b/application/client/src/app/ui/elements/menu.attachsource/template.html index 0dd71808d..b63cfec2d 100644 --- a/application/client/src/app/ui/elements/menu.attachsource/template.html +++ b/application/client/src/app/ui/elements/menu.attachsource/template.html @@ -1,14 +1,13 @@ -

{{attach().error()}}

- +

{{error}}

+ + - +
- diff --git a/application/client/src/app/ui/elements/module.ts b/application/client/src/app/ui/elements/module.ts index 98c01be2e..78fc68bd0 100644 --- a/application/client/src/app/ui/elements/module.ts +++ b/application/client/src/app/ui/elements/module.ts @@ -19,6 +19,7 @@ import { TeamworkAppletModule } from '@elements/teamwork/module'; import { TimezoneSelectorModule } from '@elements/timezones/module'; import { ComTooltipComponent } from '@elements/tooltip/component'; import { TreeModule } from '@elements/tree/module'; +import { SettingsSchemeModule } from '@elements/scheme/module'; @NgModule({ imports: [ @@ -28,6 +29,7 @@ import { TreeModule } from '@elements/tree/module'; TabsModule, RecentActionsModule, TreeModule, + SettingsSchemeModule, LocksHistoryModule, AutocompleteModule, FolderInputModule, @@ -48,6 +50,7 @@ import { TreeModule } from '@elements/tree/module'; TabsModule, RecentActionsModule, TreeModule, + SettingsSchemeModule, LocksHistoryModule, AutocompleteModule, FolderInputModule, diff --git a/application/client/src/app/ui/elements/navigator/component.ts b/application/client/src/app/ui/elements/navigator/component.ts index 8cc667853..21b944da4 100644 --- a/application/client/src/app/ui/elements/navigator/component.ts +++ b/application/client/src/app/ui/elements/navigator/component.ts @@ -16,7 +16,7 @@ import { ChangesDetector } from '@ui/env/extentions/changes'; import { State, CloseHandler } from './state'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { InputFilter } from '@elements/filter/component'; -import { Observe } from '@platform/types/observe'; +import { SessionOrigin } from '@service/session/origin'; @Component({ selector: 'app-navigator', @@ -35,7 +35,7 @@ export class Navigator @ViewChild('filter') public filterInputRef!: InputFilter; @Input() close: CloseHandler | undefined; - @Input() public observe?: Observe; + @Input() public origin: SessionOrigin | undefined; public state!: State; @@ -56,7 +56,7 @@ export class Navigator ? this.filterInputRef.getInputElementRef() : undefined; }, - this.observe, + this.origin, ); this.close !== undefined && this.state.bind(this.close); } diff --git a/application/client/src/app/ui/elements/navigator/providers/entity.ts b/application/client/src/app/ui/elements/navigator/providers/entity.ts index 295be5ac3..4b2c80078 100644 --- a/application/client/src/app/ui/elements/navigator/providers/entity.ts +++ b/application/client/src/app/ui/elements/navigator/providers/entity.ts @@ -56,11 +56,11 @@ export class Entity extends PassiveMatchee { } } - // public hash(): string { - // if (this.origin instanceof Action) { - // return `${this.origin.description().major}-${this.origin.description().minor}`; - // } else { - // return `${this.origin.name}-${this.origin.parent}`; - // } - // } + public hash(): string { + if (this.origin instanceof Action) { + return `${this.origin.description().major}-${this.origin.description().minor}`; + } else { + return `${this.origin.name}-${this.origin.parent}`; + } + } } diff --git a/application/client/src/app/ui/elements/navigator/providers/provider.files.ts b/application/client/src/app/ui/elements/navigator/providers/provider.files.ts index ec4270f98..5fe48958a 100644 --- a/application/client/src/app/ui/elements/navigator/providers/provider.files.ts +++ b/application/client/src/app/ui/elements/navigator/providers/provider.files.ts @@ -5,9 +5,8 @@ import { bridge } from '@service/bridge'; import { getFileName } from '@platform/types/files'; import { notifications, Notification } from '@ui/service/notifications'; import { FolderEntityType } from '@platform/types/bindings'; - -import * as Factory from '@platform/types/observe/factory'; import { IMenuItem } from '@ui/service/contextmenu'; +import { SessionOrigin } from '@service/session/origin'; const DEFAULT_DEEP = 5; const DEFAULT_LEN = 20000; @@ -83,13 +82,7 @@ export class Provider extends Base { this.ilc .ilc() .services.system.session.initialize() - .configure( - new Factory.File() - .asDlt() - .type(Factory.FileType.Binary) - .file(item.filename) - .get(), - ) + .configure(SessionOrigin.file(item.filename)) .catch((err: Error) => { this.ilc.log().error(`Fail to open text file; error: ${err.message}`); }); @@ -98,13 +91,7 @@ export class Provider extends Base { this.ilc .ilc() .services.system.session.initialize() - .configure( - new Factory.File() - .asDlt() - .type(Factory.FileType.PcapNG) - .file(item.filename) - .get(), - ) + .configure(SessionOrigin.file(item.filename)) .catch((err: Error) => { this.ilc.log().error(`Fail to open text file; error: ${err.message}`); }); @@ -113,13 +100,7 @@ export class Provider extends Base { this.ilc .ilc() .services.system.session.initialize() - .configure( - new Factory.File() - .asDlt() - .type(Factory.FileType.PcapLegacy) - .file(item.filename) - .get(), - ) + .configure(SessionOrigin.file(item.filename)) .catch((err: Error) => { this.ilc.log().error(`Fail to open text file; error: ${err.message}`); }); @@ -128,13 +109,7 @@ export class Provider extends Base { this.ilc .ilc() .services.system.session.initialize() - .observe( - new Factory.File() - .asText() - .type(Factory.FileType.Text) - .file(item.filename) - .get(), - ) + .observe(SessionOrigin.file(item.filename)) .catch((err: Error) => { this.ilc.log().error(`Fail to open text file; error: ${err.message}`); }); @@ -143,13 +118,7 @@ export class Provider extends Base { this.ilc .ilc() .services.system.session.initialize() - .observe( - new Factory.File() - .asParserPlugin() - .type(Factory.FileType.ParserPlugin) - .file(item.filename) - .get(), - ) + .observe(SessionOrigin.file(item.filename)) .catch((err: Error) => { this.ilc .log() @@ -165,40 +134,29 @@ export class Provider extends Base { this.ilc.log().info('More than one file detected'); return; } - const filetype = files[0].type; - if (filetype === Factory.FileType.Text) { - this.ilc - .ilc() - .services.system.session.initialize() - .observe( - new Factory.File() - .asText() - .type(filetype) - .file(item.filename) - .get(), - ) - .catch((err: Error) => { - this.ilc - .log() - .error(`Fail to open text file; error: ${err.message}`); - }); - } else { - this.ilc - .ilc() - .services.system.session.initialize() - .configure( - new Factory.File() - .type(filetype) - .file(item.filename) - .guessParser() - .get(), - ) - .catch((err: Error) => { - this.ilc - .log() - .error(`Fail to open text file; error: ${err.message}`); - }); - } + console.error(`Not implemented`); + // const filetype = files[0].type; + // if (filetype === Factory.FileType.Text) { + // this.ilc + // .ilc() + // .services.system.session.initialize() + // .observe(SessionOrigin.file(item.filename)) + // .catch((err: Error) => { + // this.ilc + // .log() + // .error(`Fail to open text file; error: ${err.message}`); + // }); + // } else { + // this.ilc + // .ilc() + // .services.system.session.initialize() + // .configure(SessionOrigin.file(item.filename)) + // .catch((err: Error) => { + // this.ilc + // .log() + // .error(`Fail to open text file; error: ${err.message}`); + // }); + // } }) .catch((error) => { this.ilc.log().error(error); diff --git a/application/client/src/app/ui/elements/navigator/providers/provider.recent.ts b/application/client/src/app/ui/elements/navigator/providers/provider.recent.ts index e9975e7fe..4b67879f4 100644 --- a/application/client/src/app/ui/elements/navigator/providers/provider.recent.ts +++ b/application/client/src/app/ui/elements/navigator/providers/provider.recent.ts @@ -25,7 +25,7 @@ export class Provider extends Base { recent .get() .then((actions: Action[]) => { - this.storage().remove(actions.map((action: Action) => action.uuid)); + this.storage().remove(actions.map((action: Action) => action.hash)); this.reload.emit(); }) .catch((err: Error) => { @@ -36,7 +36,9 @@ export class Provider extends Base { } public load(): Promise { return recent.get().then((actions: Action[]) => { - actions = actions.filter((action) => action.isSuitable(this.observe)); + actions = actions.filter((action) => + this.origin ? action.isSuitable(this.origin) : true, + ); actions.sort((a: Action, b: Action) => { return b.stat.score().recent() >= a.stat.score().recent() ? 1 : -1; }); @@ -72,7 +74,7 @@ export class Provider extends Base { { caption: 'Remove recent', handler: () => { - this.storage().remove([entity.uuid]); + this.storage().remove([entity.hash]); }, }, { diff --git a/application/client/src/app/ui/elements/navigator/providers/provider.ts b/application/client/src/app/ui/elements/navigator/providers/provider.ts index 04d6d8df4..4d22c34d9 100644 --- a/application/client/src/app/ui/elements/navigator/providers/provider.ts +++ b/application/client/src/app/ui/elements/navigator/providers/provider.ts @@ -2,7 +2,7 @@ import { IlcInterface } from '@service/ilc'; import { ChangesDetector } from '@ui/env/extentions/changes'; import { IMenuItem } from '@ui/service/contextmenu'; import { Subject } from '@platform/env/subscription'; -import { Observe } from '@platform/types/observe'; +import { SessionOrigin } from '@service/session/origin'; export interface IStatistics { title: string; @@ -23,7 +23,7 @@ export abstract class Provider { constructor( public readonly ilc: IlcInterface & ChangesDetector, public readonly index: number, - protected readonly observe: Observe | undefined, + protected readonly origin: SessionOrigin | undefined, ) {} public abstract load(): Promise; diff --git a/application/client/src/app/ui/elements/navigator/providers/providers.ts b/application/client/src/app/ui/elements/navigator/providers/providers.ts index aff7330f7..874e77132 100644 --- a/application/client/src/app/ui/elements/navigator/providers/providers.ts +++ b/application/client/src/app/ui/elements/navigator/providers/providers.ts @@ -5,11 +5,11 @@ import { Provider as ProviderFiles } from './provider.files'; import { Provider as ProviderRecent } from './provider.recent'; import { IlcInterface } from '@service/ilc'; import { ChangesDetector } from '@ui/env/extentions/changes'; -import { Observe } from '@platform/types/observe'; import * as wasm from '@loader/wasm'; import { IMenuItem } from '@ui/service/contextmenu'; +import { SessionOrigin } from '@service/session/origin'; const PROVIDERS = [ProviderRecent, ProviderFiles]; @@ -20,9 +20,9 @@ export class Providers { protected readonly ilc: IlcInterface & ChangesDetector, protected readonly matcher: wasm.Matcher, protected readonly entries: Entries, - observe: Observe | undefined, + origin: SessionOrigin | undefined, ) { - this.providers = PROVIDERS.map((Ref, i) => new Ref(ilc, i, observe)); + this.providers = PROVIDERS.map((Ref, i) => new Ref(ilc, i, origin)); this.providers.forEach((provider: Provider, i: number) => { ilc.env().subscriber.register( provider.reload.subscribe(() => { diff --git a/application/client/src/app/ui/elements/navigator/state.ts b/application/client/src/app/ui/elements/navigator/state.ts index 722ecb2cd..6cf625349 100644 --- a/application/client/src/app/ui/elements/navigator/state.ts +++ b/application/client/src/app/ui/elements/navigator/state.ts @@ -7,7 +7,8 @@ import { Holder } from '@module/matcher/holder'; import { unique } from '@platform/env/sequence'; import { INoContentActions, IStatistics } from './providers/provider'; import { IMenuItem } from '@ui/service/contextmenu'; -import { Observe } from '@platform/types/observe'; +import { ObserveOperation } from '@service/session/dependencies/stream'; +import { SessionOrigin } from '@service/session/origin'; const MOVE_SELECTION_DELAY = 150; @@ -48,12 +49,12 @@ export class State extends Holder { constructor( protected readonly ilc: IlcInterface & ChangesDetector, protected readonly filterRefGetter: () => HTMLInputElement | undefined, - protected readonly observe: Observe | undefined, + protected readonly origin: SessionOrigin | undefined, ) { super(); this.ilc = ilc; this.entries = new Entries(this.uuid, ilc, this.matcher); - this.providers = new Providers(ilc, this.matcher, this.entries, this.observe); + this.providers = new Providers(ilc, this.matcher, this.entries, this.origin); ilc.env().subscriber.register( ilc .ilc() diff --git a/application/client/src/app/ui/elements/navigator/styles.less b/application/client/src/app/ui/elements/navigator/styles.less index 2337b087a..c43bc7e6c 100644 --- a/application/client/src/app/ui/elements/navigator/styles.less +++ b/application/client/src/app/ui/elements/navigator/styles.less @@ -118,6 +118,9 @@ app-navigator { & span.major { margin-right: 12px; } + & span.minor { + padding-left: 12px; + } & span.major, & span.minor { & > span { diff --git a/application/client/src/app/ui/elements/navigator/template.html b/application/client/src/app/ui/elements/navigator/template.html index 840eb1437..8bc0e6d81 100644 --- a/application/client/src/app/ui/elements/navigator/template.html +++ b/application/client/src/app/ui/elements/navigator/template.html @@ -4,15 +4,23 @@
{{collection.title}} - +
-
-

- - + (contextmenu)="ngItemContextMenu($event, entity)" + > +

+ +

@@ -21,7 +29,7 @@

{{statistics.title}}

-

{{info}}

+

{{info}}

@@ -34,10 +42,18 @@

{{state.getNoContentActions(collection.index).title}}

-
- +
+
- diff --git a/application/client/src/app/ui/elements/recent/action.ts b/application/client/src/app/ui/elements/recent/action.ts index 96955ee90..250214bbb 100644 --- a/application/client/src/app/ui/elements/recent/action.ts +++ b/application/client/src/app/ui/elements/recent/action.ts @@ -14,15 +14,28 @@ export class WrappedAction extends Matchee { this.action = action; } + protected getIcon(): string { + const origin = this.action.setup.origin; + if ((origin as { File: string }).File) { + return 'insert_drive_file'; + } else if ((origin as { Files: string[] }).Files) { + return 'folder_open'; + } else { + return 'compare_arrows'; + } + } + public description(): { major: string; minor: string; + icon: string; } { const major: string | undefined = this.getHtmlOf('html_major'); const minor: string | undefined = this.getHtmlOf('html_minor'); return { major: major === undefined ? this.action.description().major : major, minor: minor === undefined ? this.action.description().minor : minor, + icon: this.getIcon(), }; } diff --git a/application/client/src/app/ui/elements/recent/component.ts b/application/client/src/app/ui/elements/recent/component.ts index f795b9c8d..7a2436030 100644 --- a/application/client/src/app/ui/elements/recent/component.ts +++ b/application/client/src/app/ui/elements/recent/component.ts @@ -17,8 +17,7 @@ import { ChangesDetector } from '@ui/env/extentions/changes'; import { State } from './state'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { HiddenFilter } from '@elements/filter.hidden/component'; - -import * as $ from '@platform/types/observe'; +import { SessionOrigin } from '@service/session/origin'; @Component({ selector: 'app-recent-actions', @@ -31,7 +30,7 @@ import * as $ from '@platform/types/observe'; @Initial() @Ilc() export class RecentActions extends ChangesDetector implements AfterContentInit, AfterViewInit { - @Input() public observe?: $.Observe; + @Input() public origin: SessionOrigin | undefined; @Output() public applied: EventEmitter = new EventEmitter(); @@ -45,20 +44,12 @@ export class RecentActions extends ChangesDetector implements AfterContentInit, public ngAfterContentInit(): void { this.markChangesForCheck(); - this.state = new State(this, this.observe); + this.state = new State(this, this.origin); this.env().subscriber.register( this.state.update.subscribe(() => { this.detectChanges(); }), ); - if (this.observe !== undefined) { - this.env().subscriber.register( - this.observe.subscribe(() => { - this.state.reload(); - this.detectChanges(); - }), - ); - } } public ngAfterViewInit(): void { @@ -87,7 +78,7 @@ export class RecentActions extends ChangesDetector implements AfterContentInit, { caption: 'Remove recent', handler: () => { - this.state.remove([action.uuid]); + this.state.remove([action.hash]); }, }, { diff --git a/application/client/src/app/ui/elements/recent/icon/component.ts b/application/client/src/app/ui/elements/recent/icon/component.ts deleted file mode 100644 index 39d0020c3..000000000 --- a/application/client/src/app/ui/elements/recent/icon/component.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-recent-nature-icon', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class RecentIcon { - @Input() public observe!: $.Observe; - - public nature(): { - File(): $.Origin.File.Configuration | undefined; - Concat(): $.Origin.Concat.Configuration | undefined; - Process(): $.Origin.Stream.Stream.Process.Configuration | undefined; - Serial(): $.Origin.Stream.Stream.Serial.Configuration | undefined; - TCP(): $.Origin.Stream.Stream.TCP.Configuration | undefined; - UDP(): $.Origin.Stream.Stream.UDP.Configuration | undefined; - } { - const nature = this.observe.origin.nature(); - return { - File: (): $.Origin.File.Configuration | undefined => { - return nature instanceof $.Origin.File.Configuration ? nature : undefined; - }, - Concat: (): $.Origin.Concat.Configuration | undefined => { - return nature instanceof $.Origin.Concat.Configuration ? nature : undefined; - }, - Process: (): $.Origin.Stream.Stream.Process.Configuration | undefined => { - return nature instanceof $.Origin.Stream.Stream.Process.Configuration - ? nature - : undefined; - }, - Serial: (): $.Origin.Stream.Stream.Serial.Configuration | undefined => { - return nature instanceof $.Origin.Stream.Stream.Serial.Configuration - ? nature - : undefined; - }, - TCP: (): $.Origin.Stream.Stream.TCP.Configuration | undefined => { - return nature instanceof $.Origin.Stream.Stream.TCP.Configuration - ? nature - : undefined; - }, - UDP: (): $.Origin.Stream.Stream.UDP.Configuration | undefined => { - return nature instanceof $.Origin.Stream.Stream.UDP.Configuration - ? nature - : undefined; - }, - }; - } - - public parser(): { - Dlt(): $.Parser.Dlt.Configuration | undefined; - SomeIp(): $.Parser.SomeIp.Configuration | undefined; - Text(): $.Parser.Text.Configuration | undefined; - } { - return { - Dlt: (): $.Parser.Dlt.Configuration | undefined => { - return this.observe.parser.as<$.Parser.Dlt.Configuration>( - $.Parser.Dlt.Configuration, - ); - }, - SomeIp: (): $.Parser.SomeIp.Configuration | undefined => { - return this.observe.parser.as<$.Parser.SomeIp.Configuration>( - $.Parser.SomeIp.Configuration, - ); - }, - Text: (): $.Parser.Text.Configuration | undefined => { - return this.observe.parser.as<$.Parser.Text.Configuration>( - $.Parser.Text.Configuration, - ); - }, - }; - } -} -export interface RecentIcon extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/recent/icon/styles.less b/application/client/src/app/ui/elements/recent/icon/styles.less deleted file mode 100644 index 8c753a0fd..000000000 --- a/application/client/src/app/ui/elements/recent/icon/styles.less +++ /dev/null @@ -1,26 +0,0 @@ -@import '../../../styles/variables.less'; - - -:host { - position: relative; - display: flex; - flex-direction: column; - width: 42px; - overflow: hidden; - align-items: center; - justify-content: center; - & span { - color: var(--scheme-color-2); - font-weight: 500; - padding-right: 12px; - font-size: 12px; - } - & mat-icon { - font-size: 16px; - height: 16px; - width: 16px; - margin: 0; - color: var(--scheme-color-2); - padding: 0 12px 0 0; - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/icon/template.html b/application/client/src/app/ui/elements/recent/icon/template.html deleted file mode 100644 index 9bccc5596..000000000 --- a/application/client/src/app/ui/elements/recent/icon/template.html +++ /dev/null @@ -1,8 +0,0 @@ - insert_drive_file -folder_open -code -usb -compare_arrows -compare_arrows -DLT -SomeIp \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/module.ts b/application/client/src/app/ui/elements/recent/module.ts index 755288703..171fb71d4 100644 --- a/application/client/src/app/ui/elements/recent/module.ts +++ b/application/client/src/app/ui/elements/recent/module.ts @@ -7,9 +7,6 @@ import { MatMenuModule } from '@angular/material/menu'; import { HiddenFilterModule } from '@elements/filter.hidden/module'; import { RecentActions } from './component'; -import { RecentIcon } from './icon/component'; -import { RecentNatureModule } from './nature/module'; -import { RecentParserModule } from './parser/module'; @NgModule({ imports: [ @@ -19,10 +16,8 @@ import { RecentParserModule } from './parser/module'; MatIconModule, MatMenuModule, HiddenFilterModule, - RecentNatureModule, - RecentParserModule, ], - declarations: [RecentActions, RecentIcon], + declarations: [RecentActions], exports: [RecentActions], }) export class RecentActionsModule {} diff --git a/application/client/src/app/ui/elements/recent/nature/component.ts b/application/client/src/app/ui/elements/recent/nature/component.ts deleted file mode 100644 index fd15eb92d..000000000 --- a/application/client/src/app/ui/elements/recent/nature/component.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Component, AfterContentInit, Input } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-recent-nature', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class RecentNature implements AfterContentInit { - @Input() public observe!: $.Observe; - - public ngAfterContentInit(): void { - // - } - - public as(): { - File(): $.Origin.File.Configuration | undefined; - Concat(): $.Origin.Concat.Configuration | undefined; - Process(): $.Origin.Stream.Stream.Process.Configuration | undefined; - Serial(): $.Origin.Stream.Stream.Serial.Configuration | undefined; - TCP(): $.Origin.Stream.Stream.TCP.Configuration | undefined; - UDP(): $.Origin.Stream.Stream.UDP.Configuration | undefined; - } { - const nature = this.observe.origin.nature(); - return { - File: (): $.Origin.File.Configuration | undefined => { - return nature instanceof $.Origin.File.Configuration ? nature : undefined; - }, - Concat: (): $.Origin.Concat.Configuration | undefined => { - return nature instanceof $.Origin.Concat.Configuration ? nature : undefined; - }, - Process: (): $.Origin.Stream.Stream.Process.Configuration | undefined => { - return nature instanceof $.Origin.Stream.Stream.Process.Configuration - ? nature - : undefined; - }, - Serial: (): $.Origin.Stream.Stream.Serial.Configuration | undefined => { - return nature instanceof $.Origin.Stream.Stream.Serial.Configuration - ? nature - : undefined; - }, - TCP: (): $.Origin.Stream.Stream.TCP.Configuration | undefined => { - return nature instanceof $.Origin.Stream.Stream.TCP.Configuration - ? nature - : undefined; - }, - UDP: (): $.Origin.Stream.Stream.UDP.Configuration | undefined => { - return nature instanceof $.Origin.Stream.Stream.UDP.Configuration - ? nature - : undefined; - }, - }; - } -} -export interface RecentNature extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/recent/nature/concat/component.ts b/application/client/src/app/ui/elements/recent/nature/concat/component.ts deleted file mode 100644 index 67b777a08..000000000 --- a/application/client/src/app/ui/elements/recent/nature/concat/component.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Component, AfterContentInit, Input } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -// import { bytesToStr, timestampToUTC } from '@env/str'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-recent-nature-concat', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class RecentNatureConcat implements AfterContentInit { - @Input() public origin!: $.Origin.Concat.Configuration; - - public name!: string; - public path!: string; - public size!: string; - public created!: string; - - public ngAfterContentInit(): void { - // this.name = base.name; - // this.path = base.path; - // this.size = bytesToStr(base.size); - // this.created = timestampToUTC(base.created); - } -} -export interface RecentNatureConcat extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/recent/nature/concat/styles.less b/application/client/src/app/ui/elements/recent/nature/concat/styles.less deleted file mode 100644 index 190cad77b..000000000 --- a/application/client/src/app/ui/elements/recent/nature/concat/styles.less +++ /dev/null @@ -1,15 +0,0 @@ -@import '../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - overflow: hidden; - white-space: nowrap; - font-size: 13px; - color: var(--scheme-color-3); - & > span { - color: var(--scheme-color-2); - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/nature/concat/template.html b/application/client/src/app/ui/elements/recent/nature/concat/template.html deleted file mode 100644 index e1c1dd52a..000000000 --- a/application/client/src/app/ui/elements/recent/nature/concat/template.html +++ /dev/null @@ -1 +0,0 @@ -Concating {{origin.files().length}} files \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/nature/file/component.ts b/application/client/src/app/ui/elements/recent/nature/file/component.ts deleted file mode 100644 index ded62a63b..000000000 --- a/application/client/src/app/ui/elements/recent/nature/file/component.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Component, AfterContentInit, Input } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -import { getFileName, getParentFolder } from '@platform/types/files'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-recent-nature-file', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class RecentNatureFile implements AfterContentInit { - @Input() public origin!: $.Origin.File.Configuration; - - public name!: string; - public path!: string; - - public ngAfterContentInit(): void { - this.name = getFileName(this.origin.filename()); - this.path = getParentFolder(this.origin.filename()); - } -} -export interface RecentNatureFile extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/recent/nature/file/styles.less b/application/client/src/app/ui/elements/recent/nature/file/styles.less deleted file mode 100644 index 312349ebd..000000000 --- a/application/client/src/app/ui/elements/recent/nature/file/styles.less +++ /dev/null @@ -1,19 +0,0 @@ -@import '../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - overflow: hidden; - white-space: nowrap; - font-size: 13px; - & > p { - padding: 0; - margin: 0; - color: var(--scheme-color-3); - & > span { - color: var(--scheme-color-2); - } - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/nature/file/template.html b/application/client/src/app/ui/elements/recent/nature/file/template.html deleted file mode 100644 index da8cef9bc..000000000 --- a/application/client/src/app/ui/elements/recent/nature/file/template.html +++ /dev/null @@ -1,2 +0,0 @@ -

{{name}}

-

{{path}}

\ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/nature/module.ts b/application/client/src/app/ui/elements/recent/nature/module.ts deleted file mode 100644 index 2b5c9d15d..000000000 --- a/application/client/src/app/ui/elements/recent/nature/module.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { RecentNature } from './component'; -import { RecentNatureConcat } from './concat/component'; -import { RecentNatureFile } from './file/component'; -import { RecentNatureUdp } from './udp/component'; -import { RecentNatureTcp } from './tcp/component'; -import { RecentNatureSerial } from './serial/component'; -import { RecentNatureProcess } from './process/component'; - -@NgModule({ - imports: [CommonModule], - declarations: [ - RecentNature, - RecentNatureConcat, - RecentNatureFile, - RecentNatureUdp, - RecentNatureTcp, - RecentNatureSerial, - RecentNatureProcess, - ], - exports: [RecentNature], -}) -export class RecentNatureModule {} diff --git a/application/client/src/app/ui/elements/recent/nature/process/component.ts b/application/client/src/app/ui/elements/recent/nature/process/component.ts deleted file mode 100644 index 8fdb8e12b..000000000 --- a/application/client/src/app/ui/elements/recent/nature/process/component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Component, AfterContentInit, Input } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-recent-nature-process', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class RecentNatureProcess implements AfterContentInit { - @Input() public origin!: $.Origin.Stream.Stream.Process.Configuration; - - public ngAfterContentInit(): void { - // - } -} -export interface RecentNatureProcess extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/recent/nature/process/styles.less b/application/client/src/app/ui/elements/recent/nature/process/styles.less deleted file mode 100644 index 312349ebd..000000000 --- a/application/client/src/app/ui/elements/recent/nature/process/styles.less +++ /dev/null @@ -1,19 +0,0 @@ -@import '../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - overflow: hidden; - white-space: nowrap; - font-size: 13px; - & > p { - padding: 0; - margin: 0; - color: var(--scheme-color-3); - & > span { - color: var(--scheme-color-2); - } - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/nature/process/template.html b/application/client/src/app/ui/elements/recent/nature/process/template.html deleted file mode 100644 index 8c5c072f0..000000000 --- a/application/client/src/app/ui/elements/recent/nature/process/template.html +++ /dev/null @@ -1,2 +0,0 @@ -

run: {{origin.configuration.command}}

-

from: {{origin.configuration.cwd}}

diff --git a/application/client/src/app/ui/elements/recent/nature/serial/component.ts b/application/client/src/app/ui/elements/recent/nature/serial/component.ts deleted file mode 100644 index f5f3c579e..000000000 --- a/application/client/src/app/ui/elements/recent/nature/serial/component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Component, AfterContentInit, Input } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-recent-nature-serial', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class RecentNatureSerial implements AfterContentInit { - @Input() public origin!: $.Origin.Stream.Stream.Serial.Configuration; - - public ngAfterContentInit(): void { - // - } -} -export interface RecentNatureSerial extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/recent/nature/serial/styles.less b/application/client/src/app/ui/elements/recent/nature/serial/styles.less deleted file mode 100644 index 190cad77b..000000000 --- a/application/client/src/app/ui/elements/recent/nature/serial/styles.less +++ /dev/null @@ -1,15 +0,0 @@ -@import '../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - overflow: hidden; - white-space: nowrap; - font-size: 13px; - color: var(--scheme-color-3); - & > span { - color: var(--scheme-color-2); - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/nature/serial/template.html b/application/client/src/app/ui/elements/recent/nature/serial/template.html deleted file mode 100644 index 2b2323785..000000000 --- a/application/client/src/app/ui/elements/recent/nature/serial/template.html +++ /dev/null @@ -1 +0,0 @@ -Connection to: {{origin.configuration.path}} ({{origin.configuration.baud_rate}}/{{origin.configuration.data_bits}}) \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/nature/styles.less b/application/client/src/app/ui/elements/recent/nature/styles.less deleted file mode 100644 index 856ff2de9..000000000 --- a/application/client/src/app/ui/elements/recent/nature/styles.less +++ /dev/null @@ -1,8 +0,0 @@ -@import '../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; -} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/nature/tcp/component.ts b/application/client/src/app/ui/elements/recent/nature/tcp/component.ts deleted file mode 100644 index d0768f10e..000000000 --- a/application/client/src/app/ui/elements/recent/nature/tcp/component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Component, AfterContentInit, Input } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-recent-nature-tcp', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class RecentNatureTcp implements AfterContentInit { - @Input() public origin!: $.Origin.Stream.Stream.TCP.Configuration; - - public ngAfterContentInit(): void { - // - } -} -export interface RecentNatureTcp extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/recent/nature/tcp/styles.less b/application/client/src/app/ui/elements/recent/nature/tcp/styles.less deleted file mode 100644 index 190cad77b..000000000 --- a/application/client/src/app/ui/elements/recent/nature/tcp/styles.less +++ /dev/null @@ -1,15 +0,0 @@ -@import '../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - overflow: hidden; - white-space: nowrap; - font-size: 13px; - color: var(--scheme-color-3); - & > span { - color: var(--scheme-color-2); - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/nature/tcp/template.html b/application/client/src/app/ui/elements/recent/nature/tcp/template.html deleted file mode 100644 index 42e79f9bc..000000000 --- a/application/client/src/app/ui/elements/recent/nature/tcp/template.html +++ /dev/null @@ -1 +0,0 @@ -TCP on: {{origin.configuration.bind_addr}} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/nature/template.html b/application/client/src/app/ui/elements/recent/nature/template.html deleted file mode 100644 index 43501548b..000000000 --- a/application/client/src/app/ui/elements/recent/nature/template.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/nature/udp/component.ts b/application/client/src/app/ui/elements/recent/nature/udp/component.ts deleted file mode 100644 index 7fdcc5d17..000000000 --- a/application/client/src/app/ui/elements/recent/nature/udp/component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Component, AfterContentInit, Input } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-recent-nature-udp', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class RecentNatureUdp implements AfterContentInit { - @Input() public origin!: $.Origin.Stream.Stream.UDP.Configuration; - - public ngAfterContentInit(): void { - // - } -} -export interface RecentNatureUdp extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/recent/nature/udp/styles.less b/application/client/src/app/ui/elements/recent/nature/udp/styles.less deleted file mode 100644 index 190cad77b..000000000 --- a/application/client/src/app/ui/elements/recent/nature/udp/styles.less +++ /dev/null @@ -1,15 +0,0 @@ -@import '../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - overflow: hidden; - white-space: nowrap; - font-size: 13px; - color: var(--scheme-color-3); - & > span { - color: var(--scheme-color-2); - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/nature/udp/template.html b/application/client/src/app/ui/elements/recent/nature/udp/template.html deleted file mode 100644 index 8a091f064..000000000 --- a/application/client/src/app/ui/elements/recent/nature/udp/template.html +++ /dev/null @@ -1,4 +0,0 @@ -UDP on: {{origin.configuration.bind_addr}} - - Multicast: {{origin.configuration.multicast[0].multiaddr}} ({{origin.configuration.multicast[0].interface}}) - diff --git a/application/client/src/app/ui/elements/recent/parser/component.ts b/application/client/src/app/ui/elements/recent/parser/component.ts deleted file mode 100644 index 27bad6ed8..000000000 --- a/application/client/src/app/ui/elements/recent/parser/component.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Component, AfterContentInit, Input } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-recent-parser', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class RecentParser implements AfterContentInit { - @Input() public observe!: $.Observe; - - public ngAfterContentInit(): void { - // - } - - public as(): { - Dlt(): $.Parser.Dlt.Configuration | undefined; - SomeIp(): $.Parser.SomeIp.Configuration | undefined; - Text(): $.Parser.Text.Configuration | undefined; - } { - return { - Dlt: (): $.Parser.Dlt.Configuration | undefined => { - return this.observe.parser.as<$.Parser.Dlt.Configuration>( - $.Parser.Dlt.Configuration, - ); - }, - SomeIp: (): $.Parser.SomeIp.Configuration | undefined => { - return this.observe.parser.as<$.Parser.SomeIp.Configuration>( - $.Parser.SomeIp.Configuration, - ); - }, - Text: (): $.Parser.Text.Configuration | undefined => { - return this.observe.parser.as<$.Parser.Text.Configuration>( - $.Parser.Text.Configuration, - ); - }, - }; - } -} -export interface RecentParser extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/recent/parser/dlt/component.ts b/application/client/src/app/ui/elements/recent/parser/dlt/component.ts deleted file mode 100644 index 36b5cd65a..000000000 --- a/application/client/src/app/ui/elements/recent/parser/dlt/component.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Component, AfterContentInit, Input } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -// import { bytesToStr, timestampToUTC } from '@env/str'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-recent-parser-dlt', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class RecentParserDlt implements AfterContentInit { - @Input() public parser!: $.Parser.Dlt.Configuration; - - public logLevel!: string; - public fibex: string[] = []; - public timezone: string | undefined; - - public ngAfterContentInit(): void { - if ( - this.parser.configuration.filter_config === undefined || - this.parser.configuration.filter_config.min_log_level === undefined - ) { - this.logLevel = $.Parser.Dlt.DltLogLevelNames[6]; - } else { - this.logLevel = $.Parser.Dlt.getLogLevelName( - this.parser.configuration.filter_config.min_log_level, - ); - } - if (this.parser.configuration.fibex_file_paths === undefined) { - this.fibex = []; - } else { - this.fibex = this.parser.configuration.fibex_file_paths; - } - this.timezone = this.parser.configuration.tz; - } -} -export interface RecentParserDlt extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/recent/parser/dlt/styles.less b/application/client/src/app/ui/elements/recent/parser/dlt/styles.less deleted file mode 100644 index 3b485ebc8..000000000 --- a/application/client/src/app/ui/elements/recent/parser/dlt/styles.less +++ /dev/null @@ -1,31 +0,0 @@ -@import '../../../../styles/variables.less'; - - - -:host { - position: relative; - display: block; - & span.path { - margin-left: 12px; - } - & .nowrap { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - } - & table { - margin-left: -3px; - } - & table > tr { - vertical-align: top; - } - & table * { - border-spacing: 0px; - } - & table { - & td.left-padding { - padding-left: 12px; - } - } - -} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/parser/dlt/template.html b/application/client/src/app/ui/elements/recent/parser/dlt/template.html deleted file mode 100644 index 0e8ba23f8..000000000 --- a/application/client/src/app/ui/elements/recent/parser/dlt/template.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - -
Log level:{{logLevel}} -

Bound fibex files:

-

{{fibex}}

-
-

Timezone:

-

{{timezone}}

-
diff --git a/application/client/src/app/ui/elements/recent/parser/module.ts b/application/client/src/app/ui/elements/recent/parser/module.ts deleted file mode 100644 index 1ae35bc15..000000000 --- a/application/client/src/app/ui/elements/recent/parser/module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { RecentParser } from './component'; -import { RecentParserDlt } from './dlt/component'; -import { RecentParserSomeIp } from './someip/component'; - -@NgModule({ - imports: [CommonModule], - declarations: [RecentParser, RecentParserDlt, RecentParserSomeIp], - exports: [RecentParser], -}) -export class RecentParserModule {} diff --git a/application/client/src/app/ui/elements/recent/parser/someip/component.ts b/application/client/src/app/ui/elements/recent/parser/someip/component.ts deleted file mode 100644 index d4958480d..000000000 --- a/application/client/src/app/ui/elements/recent/parser/someip/component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Component, AfterContentInit, Input } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -// import { bytesToStr, timestampToUTC } from '@env/str'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-recent-parser-someip', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class RecentParserSomeIp implements AfterContentInit { - @Input() public parser!: $.Parser.SomeIp.Configuration; - public fibex: string[] = []; - - public ngAfterContentInit(): void { - if (this.parser.configuration.fibex_file_paths === undefined) { - this.fibex = []; - } else { - this.fibex = this.parser.configuration.fibex_file_paths; - } - } -} -export interface RecentParserSomeIp extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/recent/parser/someip/styles.less b/application/client/src/app/ui/elements/recent/parser/someip/styles.less deleted file mode 100644 index 8ba5c2caa..000000000 --- a/application/client/src/app/ui/elements/recent/parser/someip/styles.less +++ /dev/null @@ -1,31 +0,0 @@ -@import '../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - padding: 6px 0; - & div.section { - position: relative; - } - & span.path { - margin-left: 12px; - } - & .nowrap { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - } - & table > tr { - vertical-align: top; - } - & table * { - border-spacing: 0px; - } - & table { - & td.left-padding { - padding-left: 12px; - } - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/parser/someip/template.html b/application/client/src/app/ui/elements/recent/parser/someip/template.html deleted file mode 100644 index 2c661cb86..000000000 --- a/application/client/src/app/ui/elements/recent/parser/someip/template.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - -
-

Bound fibex files:

-

No any fibex files

-

{{fibex}}

-
\ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/parser/styles.less b/application/client/src/app/ui/elements/recent/parser/styles.less deleted file mode 100644 index 856ff2de9..000000000 --- a/application/client/src/app/ui/elements/recent/parser/styles.less +++ /dev/null @@ -1,8 +0,0 @@ -@import '../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; -} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/parser/template.html b/application/client/src/app/ui/elements/recent/parser/template.html deleted file mode 100644 index dd91e7f74..000000000 --- a/application/client/src/app/ui/elements/recent/parser/template.html +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/application/client/src/app/ui/elements/recent/state.ts b/application/client/src/app/ui/elements/recent/state.ts index 8b4456dee..3d031e831 100644 --- a/application/client/src/app/ui/elements/recent/state.ts +++ b/application/client/src/app/ui/elements/recent/state.ts @@ -6,20 +6,19 @@ import { IlcInterface } from '@service/ilc'; import { ChangesDetector } from '@ui/env/extentions/changes'; import { Holder } from '@module/matcher'; import { Logger } from '@platform/log'; - -import * as $ from '@platform/types/observe'; +import { SessionOrigin } from '@service/session/origin'; export class State extends Holder { public actions: WrappedAction[] = []; public readonly update: Subject = new Subject(); - public readonly observe: $.Observe | undefined; + public origin: SessionOrigin | undefined; private _logger: Logger; - constructor(ilc: IlcInterface & ChangesDetector, observe?: $.Observe) { + constructor(ilc: IlcInterface & ChangesDetector, origin: SessionOrigin | undefined) { super(); - this.observe = observe; + this.origin = origin; this._logger = ilc.log(); ilc.env().subscriber.register(recent.updated.subscribe(this.reload.bind(this))); this.reload(); @@ -56,7 +55,7 @@ export class State extends Holder { recent .get() .then((actions: Action[]) => { - this.remove(actions.map((action: Action) => action.uuid)); + this.remove(actions.map((action: Action) => action.hash)); }) .catch((err: Error) => { this._logger.error(`Fail to remove all recent actions: ${err.message}`); @@ -68,7 +67,7 @@ export class State extends Holder { .get() .then((actions: Action[]) => { this.actions = actions - .filter((action) => action.isSuitable(this.observe)) + .filter((action) => (this.origin ? action.isSuitable(this.origin) : true)) .map((action) => new WrappedAction(action, this.matcher)); this.actions.sort((a: WrappedAction, b: WrappedAction) => { return b.action.stat.score().recent() >= a.action.stat.score().recent() diff --git a/application/client/src/app/ui/elements/recent/styles.less b/application/client/src/app/ui/elements/recent/styles.less index 72b72ad27..cfb91f015 100644 --- a/application/client/src/app/ui/elements/recent/styles.less +++ b/application/client/src/app/ui/elements/recent/styles.less @@ -14,25 +14,28 @@ app-recent-actions { flex-direction: row; align-items: flex-start; & div.column_0 { - + & mat-icon { + height: 16px; + width: 16px; + font-size: 16px; + padding-right: 8px; + } } & div.column_1 { flex: auto; + & p.major { + color: var(--scheme-color-0); + } + & p.minor { + color: var(--scheme-color-2); + } + & * { + user-select: none; + cursor: default; + } } &:hover { background: var(--scheme-color-hover); - &::before { - background: var(--scheme-color-hover); - } - } - &::before { - position: absolute; - display: block; - width: ~"calc(100% + 30px)"; - content: ''; - top:0; - left: -15px; - height: 100%; } & p.description { position: relative; diff --git a/application/client/src/app/ui/elements/recent/template.html b/application/client/src/app/ui/elements/recent/template.html index 53eb58ad7..d44a0c69b 100644 --- a/application/client/src/app/ui/elements/recent/template.html +++ b/application/client/src/app/ui/elements/recent/template.html @@ -5,12 +5,10 @@ (click)="onDefaultAction(action.action)" (contextmenu)="onAllActions($event, action.action)" > -
- -
+
{{action.description().icon}}
- - +

{{action.description().major}}

+

{{action.description().minor}}

diff --git a/application/client/src/app/ui/elements/scheme/component.ts b/application/client/src/app/ui/elements/scheme/component.ts new file mode 100644 index 000000000..6e5a19f4a --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/component.ts @@ -0,0 +1,50 @@ +import { + Component, + ChangeDetectorRef, + AfterContentInit, + AfterContentChecked, + Input, +} from '@angular/core'; +import { Ilc, IlcInterface } from '@env/decorators/component'; +import { Initial } from '@env/decorators/initial'; +import { ChangesDetector } from '@ui/env/extentions/changes'; +import { SchemeProvider } from './provider'; +import { FieldDesc } from '@platform/types/bindings'; + +@Component({ + selector: 'app-settings-scheme', + templateUrl: './template.html', + styleUrls: ['./styles.less'], + standalone: false, +}) +@Initial() +@Ilc() +export class SettingsScheme + extends ChangesDetector + implements AfterContentInit, AfterContentChecked +{ + @Input() provider!: SchemeProvider; + + public fields: FieldDesc[] = []; + + protected uuid: string | undefined; + + constructor(cdRef: ChangeDetectorRef) { + super(cdRef); + } + + public ngAfterContentChecked(): void { + if (this.uuid === undefined || this.provider === undefined) { + return; + } + if (this.uuid === this.provider.uuid) { + return; + } + this.ngAfterContentInit(); + } + public ngAfterContentInit(): void { + this.fields = this.provider.getFieldDescs(); + this.uuid = this.provider.uuid; + } +} +export interface SettingsScheme extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/scheme/entry/component.ts b/application/client/src/app/ui/elements/scheme/entry/component.ts new file mode 100644 index 000000000..9a1aae5f0 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/component.ts @@ -0,0 +1,172 @@ +import { + Component, + ChangeDetectorRef, + Input, + AfterViewInit, + AfterContentInit, + HostBinding, + OnDestroy, +} from '@angular/core'; +import { Ilc, IlcInterface } from '@env/decorators/component'; +import { Initial } from '@env/decorators/initial'; +import { ChangesDetector } from '@ui/env/extentions/changes'; +import { SchemeProvider, ForcedValueChanges } from '../provider'; +import { FieldDesc, LazyFieldDesc, StaticFieldDesc, ValueInput } from '@platform/types/bindings'; +import { ChangeEvent, Element } from './inner/element'; +import { WrappedField } from '../field'; + +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatCardModule } from '@angular/material/card'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSelectModule } from '@angular/material/select'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { SchemeEntryElement } from './inner/component'; +import { FieldCategory } from './inner/element/inner'; + +@Component({ + selector: 'app-settings-scheme-entry', + templateUrl: './template.html', + styleUrls: ['./styles.less'], + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + MatCheckboxModule, + MatCardModule, + MatDividerModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatSelectModule, + MatProgressBarModule, + SchemeEntryElement, + ], + standalone: true, +}) +@Initial() +@Ilc() +export class SchemeEntry + extends ChangesDetector + implements AfterViewInit, AfterContentInit, OnDestroy +{ + @Input() provider!: SchemeProvider; + @Input() field!: FieldDesc; + + @HostBinding('class') classes = ``; + + public pending: LazyFieldDesc | undefined; + public loaded: StaticFieldDesc | undefined; + public element: Element | undefined; + public name!: string; + public desc: string | undefined; + public error: string | undefined; + + constructor(cdRef: ChangeDetectorRef) { + super(cdRef); + } + + public ngOnDestroy(): void { + this.element && this.element.destroy(); + } + + public ngAfterContentInit(): void { + this.pending = (this.field as { Lazy: LazyFieldDesc }).Lazy; + this.loaded = (this.field as { Static: StaticFieldDesc }).Static; + const wrapped = new WrappedField(this.field); + this.name = wrapped.name; + this.desc = wrapped.desc.trim() === '' ? undefined : wrapped.desc; + this.init(); + this.env().subscriber.register( + this.provider.subjects.get().loaded.subscribe((loaded: StaticFieldDesc) => { + if (!this.pending || this.pending.id !== loaded.id) { + return; + } + this.loaded = loaded; + this.pending = undefined; + this.init(); + this.detectChanges(); + }), + ); + } + + public ngAfterViewInit(): void { + this.detectChanges(); + } + + init() { + if (this.loaded) { + this.element = new Element(this.loaded.id, this.loaded.interface, this.provider); + this.env().subscriber.register( + this.element.subjects.get().changed.subscribe(this.onSelfChanges.bind(this)), + ); + this.env().subscriber.register( + this.provider.subjects.get().forced.subscribe(this.onFieldsChanges.bind(this)), + ); + this.env().subscriber.register( + this.provider.subjects.get().error.subscribe(this.onError.bind(this)), + ); + const value = this.element.getValue(); + value && this.provider.setValue(this.loaded.id, value); + this.element.loaded(); + if (this.desc !== undefined) { + switch (this.element.getFieldCategory()) { + case FieldCategory.Inline: + this.classes = `field-${FieldCategory.Row}`; + break; + case FieldCategory.Block: + case FieldCategory.Row: + this.classes = `field-${this.element.getFieldCategory()}`; + break; + } + } else { + this.classes = `field-${this.element.getFieldCategory()}`; + } + return; + } + } + + protected onError(errs: Map) { + const prev = this.error; + if (!this.element || !this.loaded) { + this.error = undefined; + return; + } + this.error = errs.get(this.loaded.id); + if (prev !== this.error) { + this.detectChanges(); + } + } + + protected onSelfChanges(event: ChangeEvent) { + if (!this.element || !this.loaded) { + return; + } + const value = this.element.getValue(); + if (value) { + if (this.loaded.binding) { + this.provider.force(this.loaded.binding, event.inner); + } + this.provider.setValue(new WrappedField(this.field).id, value); + } + } + + protected onFieldsChanges(event: ForcedValueChanges) { + if (!this.loaded || !this.element) { + return; + } + if (event.target !== this.loaded.id) { + return; + } + this.element.setValue(event.value); + const value = this.element.getValue(); + if (value) { + this.provider.setValue(this.loaded.id, value); + } + } +} +export interface SchemeEntry extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/complex/files_selector/component.ts b/application/client/src/app/ui/elements/scheme/entry/inner/complex/files_selector/component.ts new file mode 100644 index 000000000..a6e8a01bc --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/files_selector/component.ts @@ -0,0 +1,105 @@ +import { Component, ChangeDetectorRef, Input, AfterContentInit } from '@angular/core'; +import { Ilc, IlcInterface } from '@env/decorators/component'; +import { Initial } from '@env/decorators/initial'; +import { ChangesDetector } from '@ui/env/extentions/changes'; +import { Element, FilesFolderSelectorElement } from '../../element'; +import { bridge } from '@service/bridge'; +import { File } from '@platform/types/files'; +import { FilesFoldersSelectorTarget } from '../../element/files_selector'; + +interface Path { + name: string; + path: string; +} + +@Component({ + selector: 'app-settings-scheme-files-selector', + templateUrl: './template.html', + styleUrls: ['./styles.less'], + standalone: false, +}) +@Initial() +@Ilc() +export class FilesSelector extends ChangesDetector implements AfterContentInit { + public get FilesFoldersSelectorTarget(): typeof FilesFoldersSelectorTarget { + return FilesFoldersSelectorTarget; + } + @Input() element!: Element; + @Input() inner!: FilesFolderSelectorElement; + + public paths: Path[] = []; + + constructor(cdRef: ChangeDetectorRef) { + super(cdRef); + } + + public ngAfterContentInit(): void {} + + public ngAddTarget() { + function selectFile(exts: string): Promise { + if (exts.trim() === '') { + return bridge.files().select.any(); + } else { + return bridge.files().select.custom(exts); + } + } + switch (this.inner.target) { + case FilesFoldersSelectorTarget.File: + case FilesFoldersSelectorTarget.Files: + selectFile(this.inner.exts.join(',')) + .then((paths: File[]) => { + paths = paths.filter((added) => { + return ( + this.paths.find((exist) => exist.path === added.filename) === + undefined + ); + }); + this.paths = this.paths.concat( + paths.map((file) => { + return { path: file.filename, name: file.name }; + }), + ); + }) + .catch((err: Error) => { + this.log().error(`Fail to open xml (fibex) file(s): ${err.message}`); + }) + .finally(() => { + this.detectChanges(); + }); + + break; + case FilesFoldersSelectorTarget.Folders: + case FilesFoldersSelectorTarget.Folder: + bridge + .folders() + .select() + .then((paths: string[]) => { + if (this.inner.target === FilesFoldersSelectorTarget.Folder) { + if (paths.length > 0) { + this.inner.value = [paths[0]]; + this.element.change(); + } + } else { + this.paths = this.paths.concat( + paths.map((path) => { + return { path, name: path }; + }), + ); + } + }) + .catch((err: Error) => { + this.log().error(`Fail to open xml (fibex) file(s): ${err.message}`); + }) + .finally(() => { + this.detectChanges(); + }); + break; + } + } + + public ngOnRemovePath(path: string) { + this.paths = this.paths.filter((item) => item.path !== path); + this.detectChanges(); + } +} +export interface FilesSelector extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/module.ts b/application/client/src/app/ui/elements/scheme/entry/inner/complex/files_selector/module.ts similarity index 56% rename from application/client/src/app/ui/tabs/observe/module.ts rename to application/client/src/app/ui/elements/scheme/entry/inner/complex/files_selector/module.ts index cf0195494..54fa7e886 100644 --- a/application/client/src/app/ui/tabs/observe/module.ts +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/files_selector/module.ts @@ -1,30 +1,29 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; -import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatChipsModule } from '@angular/material/chips'; import { MatSelectModule } from '@angular/material/select'; +import { MatListModule } from '@angular/material/list'; +import { MatIcon } from '@angular/material/icon'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatFormFieldModule } from '@angular/material/form-field'; -import { FileModule } from './origin/file/module'; -import { ConcatModule } from './origin/concat/module'; -import { StreamModule } from './origin/stream/module'; - -import { TabObserve } from './component'; +import { FilesSelector } from './component'; @NgModule({ imports: [ CommonModule, + MatButtonModule, + MatChipsModule, + MatSelectModule, + MatListModule, + MatIcon, FormsModule, ReactiveFormsModule, - MatButtonModule, MatFormFieldModule, - MatSelectModule, - FileModule, - ConcatModule, - StreamModule, ], - declarations: [TabObserve], - exports: [TabObserve], - bootstrap: [TabObserve], + declarations: [FilesSelector], + exports: [FilesSelector], + bootstrap: [FilesSelector], }) -export class ObserveModule {} +export class FilesSelectorModule {} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/complex/files_selector/styles.less b/application/client/src/app/ui/elements/scheme/entry/inner/complex/files_selector/styles.less new file mode 100644 index 000000000..643992b4c --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/files_selector/styles.less @@ -0,0 +1,21 @@ +@import '../../../../../../styles/variables.less'; + + +:host { + position: relative; + display: block; + div.folder-input { + display: flex; + flex-direction: row; + justify-items: center; + & button { + margin-left: 12px; + } + & input { + background: var(--scheme-color-4); + border: thin solid var(--scheme-color-3); + color: var(--scheme-color-0); + outline: none; + } + } +} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/complex/files_selector/template.html b/application/client/src/app/ui/elements/scheme/entry/inner/complex/files_selector/template.html new file mode 100644 index 000000000..86ab0b3a0 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/files_selector/template.html @@ -0,0 +1,30 @@ + +

+ + +
+
+ + +
+ + + {{path.name}} + + + +
+
diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/complex/module.ts b/application/client/src/app/ui/elements/scheme/entry/inner/complex/module.ts new file mode 100644 index 000000000..24546ed89 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { NestedDictionaryModule } from './nested_dictionary/module'; +import { FilesSelectorModule } from './files_selector/module'; +import { TimezoneSelectorModule } from './timezone/module'; + +@NgModule({ + imports: [ + CommonModule, + NestedDictionaryModule, + FilesSelectorModule, + TimezoneSelectorModule, + ], + exports: [ + NestedDictionaryModule, + FilesSelectorModule, + TimezoneSelectorModule, + ], +}) +export class ComplexFieldsModule {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/component.ts b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/component.ts similarity index 65% rename from application/client/src/app/ui/tabs/observe/parsers/extra/dlt/component.ts rename to application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/component.ts index eb817b3b7..714b82d5f 100644 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/component.ts +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/component.ts @@ -1,55 +1,30 @@ -import { - Component, - ChangeDetectorRef, - Input, - AfterContentInit, - AfterViewInit, -} from '@angular/core'; +import { Component, ChangeDetectorRef, Input, AfterContentInit } from '@angular/core'; import { Ilc, IlcInterface } from '@env/decorators/component'; import { Initial } from '@env/decorators/initial'; import { ChangesDetector } from '@ui/env/extentions/changes'; -import { bytesToStr, timestampToUTC } from '@env/str'; import { State } from './state'; -import { Observe } from '@platform/types/observe'; +import { Element, NestedDictionaryElement } from '../../element'; @Component({ - selector: 'app-el-dlt-extra', + selector: 'app-settings-scheme-nested-dictionary', templateUrl: './template.html', styleUrls: ['./styles.less'], standalone: false, }) @Initial() @Ilc() -export class DltExtraConfiguration - extends ChangesDetector - implements AfterContentInit, AfterViewInit -{ - @Input() observe!: Observe; +export class NestedDictionary extends ChangesDetector implements AfterContentInit { + @Input() element!: Element; + @Input() inner!: NestedDictionaryElement; protected state!: State; - public bytesToStr = bytesToStr; - public timestampToUTC = timestampToUTC; - public error: string | undefined; - constructor(cdRef: ChangeDetectorRef) { super(cdRef); } public ngAfterContentInit(): void { - this.state = new State(this.observe); - this.state.bind(this); - } - - public ngAfterViewInit(): void { - this.state - .struct() - .load() - .catch((err: Error) => { - this.log().error(`Fail load DLT statistics: ${err.message}`); - this.error = err.message; - this.detectChanges(); - }); + this.state = new State(this.inner.items, this.inner.dictionary, this.element); } public ngOnEntitySelect() { @@ -96,9 +71,9 @@ export class DltExtraConfiguration caption: 'Select with fotal', handler: () => { this.state.structure.forEach((section) => { - section.entities.forEach((e) => { - e.log_fatal > 0 && e.select(); - }); + // section.entities.forEach((e) => { + // e.log_fatal > 0 && e.select(); + // }); }); after(); }, @@ -107,9 +82,9 @@ export class DltExtraConfiguration caption: 'Select with errors', handler: () => { this.state.structure.forEach((section) => { - section.entities.forEach((e) => { - e.log_error > 0 && e.select(); - }); + // section.entities.forEach((e) => { + // e.log_error > 0 && e.select(); + // }); }); after(); }, @@ -118,9 +93,9 @@ export class DltExtraConfiguration caption: 'Select with warnings', handler: () => { this.state.structure.forEach((section) => { - section.entities.forEach((e) => { - e.log_warning > 0 && e.select(); - }); + // section.entities.forEach((e) => { + // e.log_warning > 0 && e.select(); + // }); }); after(); }, @@ -130,9 +105,9 @@ export class DltExtraConfiguration caption: 'Unselect without fotal', handler: () => { this.state.structure.forEach((section) => { - section.entities.forEach((e) => { - e.log_fatal === 0 && e.unselect(); - }); + // section.entities.forEach((e) => { + // e.log_fatal === 0 && e.unselect(); + // }); }); after(); }, @@ -141,9 +116,9 @@ export class DltExtraConfiguration caption: 'Unselect without errors', handler: () => { this.state.structure.forEach((section) => { - section.entities.forEach((e) => { - e.log_error === 0 && e.unselect(); - }); + // section.entities.forEach((e) => { + // e.log_error === 0 && e.unselect(); + // }); }); after(); }, @@ -152,9 +127,9 @@ export class DltExtraConfiguration caption: 'Unselect without warnings', handler: () => { this.state.structure.forEach((section) => { - section.entities.forEach((e) => { - e.log_warning === 0 && e.unselect(); - }); + // section.entities.forEach((e) => { + // e.log_warning === 0 && e.unselect(); + // }); }); after(); }, @@ -165,4 +140,4 @@ export class DltExtraConfiguration }); } } -export interface DltExtraConfiguration extends IlcInterface {} +export interface NestedDictionary extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/module.ts b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/module.ts new file mode 100644 index 000000000..46a440d97 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/module.ts @@ -0,0 +1,25 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatCardModule } from '@angular/material/card'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatTableModule } from '@angular/material/table'; +import { MatSortModule } from '@angular/material/sort'; + +import { NestedDictionary } from './component'; +import { NestedDictionaryStructure } from './structure/component'; + +@NgModule({ + imports: [ + CommonModule, + MatCardModule, + MatDividerModule, + MatProgressBarModule, + MatTableModule, + MatSortModule, + ], + declarations: [NestedDictionary, NestedDictionaryStructure], + exports: [NestedDictionary], + bootstrap: [NestedDictionary, NestedDictionaryStructure], +}) +export class NestedDictionaryModule {} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/state.ts b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/state.ts new file mode 100644 index 000000000..d8fc76c2d --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/state.ts @@ -0,0 +1,146 @@ +import { Section } from './structure/section'; +import { Summary } from './summary'; +import { DictionaryEntities } from './structure/statentity'; +import { NestedDictionaryStructure } from '../../element'; +import { Holder as MatcherHolder } from '@module/matcher'; +import { Element } from '../../element'; + +export const ENTITIES = { + app_ids: 'app_ids', + context_ids: 'context_ids', + ecu_ids: 'ecu_ids', +}; + +export const NAMES: { [key: string]: string } = { + [ENTITIES.app_ids]: 'Applications', + [ENTITIES.context_ids]: 'Contexts', + [ENTITIES.ecu_ids]: 'ECUs', +}; + +export class State extends MatcherHolder { + public structure: Section[] = []; + public summary: + | { + total: Summary; + selected: Summary; + } + | undefined; + + constructor( + protected readonly data: NestedDictionaryStructure, + protected readonly dictionary: Map, + protected readonly element: Element, + ) { + super(); + this.struct().build(new Map()); + } + + public getName(key: string): string { + const label = this.dictionary.get(key); + return label ? label : key; + } + + public getSelectedEntities(): DictionaryEntities[] { + let selected: DictionaryEntities[] = []; + this.structure.forEach((section) => { + selected = selected.concat(section.getSelected()); + }); + return selected; + } + + public struct(): { + build(preselection: Map): void; + filter(value: string): void; + } { + return { + build: (preselection: Map): void => { + const structure: Section[] = []; + this.data.forEach((section, key_section) => { + const selected = preselection.get(key_section); + const entities: DictionaryEntities[] = []; + section.forEach((values, key_entity) => { + if (!this.summary) { + const map = new Map(); + values.keys().forEach((key) => { + map.set(key, 0); + }); + this.summary = { total: new Summary(map), selected: new Summary(map) }; + } + const entity = new DictionaryEntities( + key_entity, + key_section, + values, + this.matcher, + ); + if (selected && selected.includes(key_entity)) { + entity.select(); + } + entities.push(entity); + }); + structure.push(new Section(key_section, key_section).fill(entities)); + }); + this.structure = structure; + this.buildSummary().all(); + }, + filter: (value: string): void => { + this.matcher.search(value); + this.structure.forEach((structure) => { + structure.entities.sort( + (a: DictionaryEntities, b: DictionaryEntities) => + b.getScore() - a.getScore(), + ); + structure.update.emit(); + }); + }, + }; + } + + public buildSummary(): { + total(): void; + selected(): void; + all(): void; + } { + return { + total: (): void => { + const total = this.summary ? this.summary.total : undefined; + if (!total) { + return; + } + total.reset(); + this.structure.forEach((structure) => { + structure.entities.forEach((entity) => { + total.inc(entity); + }); + }); + }, + selected: (): void => { + const selected = this.summary ? this.summary.selected : undefined; + if (!selected) { + return; + } + selected.reset(); + const data: Map = new Map(); + this.structure.forEach((structure) => { + structure.entities.forEach((entity) => { + if (!entity.selected) { + return; + } + selected.inc(entity); + let entries = data.get(structure.key); + if (!entries) { + entries = []; + } + entries.push(entity.id); + data.set(structure.key, entries); + }); + }); + this.element.setValue(data); + this.element.change(); + }, + all: (): void => { + this.buildSummary().total(); + this.buildSummary().selected(); + }, + }; + } +} diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/component.ts b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/component.ts similarity index 60% rename from application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/component.ts rename to application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/component.ts index a958c96ce..c47ce542b 100644 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/component.ts +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/component.ts @@ -14,62 +14,48 @@ import { Ilc, IlcInterface } from '@env/decorators/component'; import { ChangesDetector } from '@ui/env/extentions/changes'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; -import { StatEntity } from './statentity'; +import { DictionaryEntities } from './statentity'; import { Section } from './section'; import { MatTable } from '@angular/material/table'; - -export const COLUMNS = { - id: 'id', - non_log: 'non_log', - log_fatal: 'log_fatal', - log_error: 'log_error', - log_warning: 'log_warning', - log_info: 'log_info', - log_debug: 'log_debug', - log_verbose: 'log_verbose', - log_invalid: 'log_invalid', -}; +import { State } from '../state'; @Component({ - selector: 'app-el-dlt-extra-structure', + selector: 'app-settings-scheme-nested-dictionary-structure', templateUrl: './template.html', styleUrls: ['./styles.less'], encapsulation: ViewEncapsulation.None, standalone: false, }) @Ilc() -export class DltExtraConfigurationStructure +export class NestedDictionaryStructure extends ChangesDetector implements AfterContentInit, AfterViewInit { - columns: string[] = [ - COLUMNS.id, - COLUMNS.log_fatal, - COLUMNS.log_error, - COLUMNS.log_warning, - COLUMNS.log_debug, - COLUMNS.log_info, - COLUMNS.log_verbose, - COLUMNS.non_log, - ]; - @Input() section!: Section; - @Output() select: EventEmitter = new EventEmitter(); + @Input() state!: State; + + @Output() select: EventEmitter = new EventEmitter(); @ViewChild(MatSort) sort!: MatSort; @ViewChild(MatTable) table!: MatTable; - public data!: MatTableDataSource; + public data!: MatTableDataSource; + public keys: string[] = []; + public columns: string[] = []; constructor(cdRef: ChangeDetectorRef, private _sanitizer: DomSanitizer) { super(cdRef); } public ngAfterContentInit(): void { - this.data = new MatTableDataSource(this.section.entities); + this.data = new MatTableDataSource(this.section.entities); + this.keys = this.section.keys(); + this.columns = ['id'].concat(this.section.keys()); this.env().subscriber.register( this.section.update.subscribe(() => { - this.data.data = this.section.entities.filter((e: StatEntity) => !e.hidden()); + this.data.data = this.section.entities.filter( + (e: DictionaryEntities) => !e.hidden(), + ); this.table.renderRows(); this.detectChanges(); }), @@ -78,6 +64,10 @@ export class DltExtraConfigurationStructure public ngAfterViewInit(): void { this.data.sort = this.sort; + this.data.sortingDataAccessor = (item, property) => { + const value = property === 'id' ? item.id : item.values[property]; + return typeof value === 'string' ? value.toLowerCase() : value; + }; } public ngOnSortChange() { @@ -85,7 +75,7 @@ export class DltExtraConfigurationStructure this.table.renderRows(); } - public ngOnRowSelect(entity: StatEntity) { + public ngOnRowSelect(entity: DictionaryEntities) { entity.toggle(); this.select.emit(entity); this.detectChanges(); @@ -95,4 +85,4 @@ export class DltExtraConfigurationStructure return this._sanitizer.bypassSecurityTrustHtml(html); } } -export interface DltExtraConfigurationStructure extends IlcInterface {} +export interface NestedDictionaryStructure extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/section.ts b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/section.ts similarity index 56% rename from application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/section.ts rename to application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/section.ts index 46180a6ae..d1adb7203 100644 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/section.ts +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/section.ts @@ -1,23 +1,27 @@ import { Subject } from '@platform/env/subscription'; -import { StatEntity } from './statentity'; +import { DictionaryEntities } from './statentity'; export class Section { public key: string; public name: string; public update: Subject = new Subject(); - public entities: StatEntity[] = []; + public entities: DictionaryEntities[] = []; constructor(key: string, name: string) { this.key = key; this.name = name; } - public getSelected(): StatEntity[] { + public getSelected(): DictionaryEntities[] { return this.entities.filter((f) => f.selected); } - public fill(entities: StatEntity[]): Section { + public fill(entities: DictionaryEntities[]): Section { this.entities = entities; return this; } + + public keys(): string[] { + return this.entities.length > 0 ? this.entities[0].keys() : []; + } } diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/statentity.ts b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/statentity.ts new file mode 100644 index 000000000..8bc572dd1 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/statentity.ts @@ -0,0 +1,52 @@ +import { Matchee } from '@module/matcher'; + +import * as wasm from '@loader/wasm'; + +export class DictionaryEntities extends Matchee { + public selected: boolean = false; + public values: { [key: string]: string | number } = {}; + constructor( + public readonly id: string, + public readonly parent: string, + public readonly data: Map, + protected readonly matcher: wasm.Matcher, + ) { + super(matcher, { id: id }); + data.forEach((value, key) => { + this.values[key] = value; + }); + } + + public keys(): string[] { + return Array.from(this.data.keys()); + } + + public html(): string { + const html: string | undefined = this.getHtmlOf('html_id'); + return html === undefined ? this.id : html; + } + + public hash(): string { + return `${this.parent}-${this.id}`; + } + + public equal(entity: DictionaryEntities): boolean { + return entity.hash() === this.hash(); + } + + public toggle() { + this.selected = !this.selected; + } + + public select() { + this.selected = true; + } + + public unselect() { + this.selected = false; + } + + public hidden(): boolean { + return this.getScore() === 0; + } +} diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/styles.less b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/styles.less similarity index 89% rename from application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/styles.less rename to application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/styles.less index 9c0dd96e7..36b0c8d9c 100644 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/styles.less +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/styles.less @@ -1,7 +1,7 @@ -@import '../../../../../../styles/variables.less'; +@import '../../../../../../../styles/variables.less'; -app-el-dlt-extra-structure { +app-settings-scheme-nested-dictionary-structure { position: relative; display: block; width: 100%; diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/template.html b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/template.html new file mode 100644 index 000000000..61e2ca51b --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/structure/template.html @@ -0,0 +1,24 @@ +

{{state.getName(section.name)}}

+ + + + + + + + + + + +
+ ID + + + + {{state.getName(key)}} + {{element.values[key]}}
diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/styles.less b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/styles.less similarity index 91% rename from application/client/src/app/ui/tabs/observe/parsers/general/styles.less rename to application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/styles.less index be92722fb..7a70beb85 100644 --- a/application/client/src/app/ui/tabs/observe/parsers/general/styles.less +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/styles.less @@ -1,8 +1,8 @@ -@import '../../../../styles/variables.less'; +@import '../../../../../../styles/variables.less'; :host { - position: absolute; + position: relative; display: block; top: 0; left: 0; @@ -83,19 +83,21 @@ position: sticky; bottom: 0; z-index: 1; - box-shadow: 0px 0px 16px rgba(0,0,0,0.4); + box-shadow: 0px 0px 16px var(--scheme-color-5); } } & .table-fill { - width: 100%; + width: auto; } & .info { & .caption { text-align: left; padding-right: 12px; + width: 150px; } & .value { text-align: left; + width: 100px; } } diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/summary.ts b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/summary.ts new file mode 100644 index 000000000..a673b2e15 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/summary.ts @@ -0,0 +1,52 @@ +import { DictionaryEntities } from './structure/statentity'; + +const ROWS = 3; + +export class Summary { + public rows: Map[] = []; + + protected convert(data: Map) { + const perRows = Math.floor(data.size / ROWS); + let row = 0; + data.forEach((value, key) => { + if (!this.rows[row]) { + this.rows.push(new Map()); + } + this.rows[row].set(key, value); + if (this.rows[row].size >= perRows) { + row += 1; + } + }); + } + + protected getRow(key: string): Map | undefined { + return this.rows.find((row) => row.has(key)); + } + + constructor(data: Map) { + this.convert(data); + } + + public inc(entity: DictionaryEntities) { + entity.data.forEach((value: string | number, key: string) => { + const row = this.getRow(key); + if (!row) { + return; + } + const current = row.get(key); + if (typeof value === 'number') { + row.set(key, current ? value + current : value); + } else { + row.set(key, current ? current + 1 : 1); + } + }); + } + + public reset() { + this.rows.forEach((row) => row.keys().forEach((key) => row.set(key, 0))); + } + + public count(): number { + return this.rows.map((row) => row.size).reduce((acc, val) => acc + val, 0); + } +} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/template.html b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/template.html new file mode 100644 index 000000000..021c649fe --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/nested_dictionary/template.html @@ -0,0 +1,47 @@ + + + + + + + + +
+

+ Summary: {{state.summary.total.count()}} +

+ + + + + + + +
+ {{state.getName(column.key)}} + {{column.value}}
+
+

+ Selected: {{state.summary.selected.count()}} +

+ + + + + + + +
+ {{state.getName(column.key)}} + {{column.value}}
+
+
+ +
+
diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/complex/timezone/component.ts b/application/client/src/app/ui/elements/scheme/entry/inner/complex/timezone/component.ts new file mode 100644 index 000000000..8bcc22355 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/timezone/component.ts @@ -0,0 +1,72 @@ +import { Component, ChangeDetectorRef, Input, AfterContentInit } from '@angular/core'; +import { Ilc, IlcInterface } from '@env/decorators/component'; +import { Initial } from '@env/decorators/initial'; +import { ChangesDetector } from '@ui/env/extentions/changes'; +import { Element, TimezoneSelectorElement } from '../../element'; +import { components } from '@env/decorators/initial'; +import { Timezone } from '@ui/elements/timezones/timezone'; + +interface Tz { + name: string; + utc: string; + offset: number; +} + +@Component({ + selector: 'app-settings-scheme-tz-selector', + templateUrl: './template.html', + styleUrls: ['./styles.less'], + standalone: false, +}) +@Initial() +@Ilc() +export class TimezoneSelector extends ChangesDetector implements AfterContentInit { + @Input() element!: Element; + @Input() inner!: TimezoneSelectorElement; + + public tz: Tz | undefined; + + constructor(cdRef: ChangeDetectorRef) { + super(cdRef); + } + + public ngAfterContentInit(): void {} + + public ngOnSelect() { + const subscription = this.ilc() + .services.ui.popup.open({ + component: { + factory: components.get('app-elements-timezone-selector'), + inputs: { + selected: (timezone: Timezone): void => { + if (timezone.name.toLowerCase().startsWith('utc')) { + this.tz = undefined; + } else { + this.tz = { + name: timezone.name, + offset: timezone.offset, + utc: timezone.utc, + }; + } + this.element.setValue(this.tz ? this.tz.offset : -1); + this.element.change(); + this.detectChanges(); + }, + }, + }, + closeOnKey: 'Escape', + size: { width: 350 }, + uuid: 'app-elements-timezone-selector', + }) + .subjects.get() + .closed.subscribe(() => { + subscription.unsubscribe(); + }); + } + + public ngOnDrop() { + this.tz = undefined; + this.detectChanges(); + } +} +export interface TimezoneSelector extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/complex/timezone/module.ts b/application/client/src/app/ui/elements/scheme/entry/inner/complex/timezone/module.ts new file mode 100644 index 000000000..433b2d034 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/timezone/module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIcon } from '@angular/material/icon'; + +import { TimezoneSelector } from './component'; + +@NgModule({ + imports: [CommonModule, MatButtonModule, MatIcon], + declarations: [TimezoneSelector], + exports: [TimezoneSelector], + bootstrap: [TimezoneSelector], +}) +export class TimezoneSelectorModule {} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/complex/timezone/styles.less b/application/client/src/app/ui/elements/scheme/entry/inner/complex/timezone/styles.less new file mode 100644 index 000000000..05e40f65d --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/timezone/styles.less @@ -0,0 +1,18 @@ +@import '../../../../../../styles/variables.less'; + + +:host { + position: relative; + display: flex; + flex-direction: row; + align-items: center; + & > * { + margin: 6px; + } + & > *:first-child { + margin-left: 12px; + } + & > *:last-child { + margin-right: 12px; + } +} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/complex/timezone/template.html b/application/client/src/app/ui/elements/scheme/entry/inner/complex/timezone/template.html new file mode 100644 index 000000000..2923a4d45 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/complex/timezone/template.html @@ -0,0 +1,7 @@ + + +

Not selected

+

{{tz.name}} ({{tz.utc}})

diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/component.ts b/application/client/src/app/ui/elements/scheme/entry/inner/component.ts new file mode 100644 index 000000000..bca6e1ba6 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/component.ts @@ -0,0 +1,144 @@ +import { + Component, + ChangeDetectorRef, + Input, + AfterViewInit, + AfterContentInit, + OnDestroy, +} from '@angular/core'; +import { Ilc, IlcInterface } from '@env/decorators/component'; +import { Initial } from '@env/decorators/initial'; +import { ChangesDetector } from '@ui/env/extentions/changes'; +import { SchemeProvider } from '../../provider'; +import { Element } from './element'; + +import { ComplexFieldsModule } from './complex/module'; + +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatCardModule } from '@angular/material/card'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSelectModule } from '@angular/material/select'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatIcon } from '@angular/material/icon'; + +import * as els from './element'; +import { FieldCategory } from './element/inner'; + +@Component({ + selector: 'app-settings-scheme-element', + templateUrl: './template.html', + styleUrls: ['./styles.less'], + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + MatCheckboxModule, + MatCardModule, + MatDividerModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatSelectModule, + MatProgressBarModule, + MatIcon, + ComplexFieldsModule, + SchemeEntryElement, + ], + standalone: true, +}) +@Initial() +@Ilc() +export class SchemeEntryElement + extends ChangesDetector + implements OnDestroy, AfterViewInit, AfterContentInit +{ + @Input() provider!: SchemeProvider; + @Input() element!: Element; + + public elCheckboxElement: els.CheckboxElement | undefined; + public elFilesFolderSelectorElement: els.FilesFolderSelectorElement | undefined; + public elInputElement: els.InputElement | undefined; + public elListElement: els.ListElement | undefined; + public elKeyValueElement: els.KeyValueElement | undefined; + public elNamedValuesElement: els.NamedValuesElement | undefined; + public elNestedDictionaryElement: els.NestedDictionaryElement | undefined; + public elTimezoneSelectorElement: els.TimezoneSelectorElement | undefined; + public elFieldsCollectionElement: els.FieldsCollectionElement | undefined; + + public inline: boolean = false; + + constructor(cdRef: ChangeDetectorRef) { + super(cdRef); + } + + public ngOnDestroy(): void { + this.element.destroy(); + } + + public ngAfterContentInit(): void { + this.inline = this.element.getFieldCategory() == FieldCategory.Inline; + this.elCheckboxElement = + this.element.inner instanceof els.CheckboxElement ? this.element.inner : undefined; + this.elFilesFolderSelectorElement = + this.element.inner instanceof els.FilesFolderSelectorElement + ? this.element.inner + : undefined; + this.elInputElement = + this.element.inner instanceof els.InputElement ? this.element.inner : undefined; + this.elListElement = + this.element.inner instanceof els.ListElement ? this.element.inner : undefined; + this.elKeyValueElement = + this.element.inner instanceof els.KeyValueElement ? this.element.inner : undefined; + this.elNamedValuesElement = + this.element.inner instanceof els.NamedValuesElement ? this.element.inner : undefined; + this.elNestedDictionaryElement = + this.element.inner instanceof els.NestedDictionaryElement + ? this.element.inner + : undefined; + this.elTimezoneSelectorElement = + this.element.inner instanceof els.TimezoneSelectorElement + ? this.element.inner + : undefined; + this.elFieldsCollectionElement = + this.element.inner instanceof els.FieldsCollectionElement + ? this.element.inner + : undefined; + } + + public ngAfterViewInit(): void { + this.env().subscriber.register( + this.element.subjects.get().loaded.subscribe(() => { + this.detectChanges(); + }), + ); + if (this.elFieldsCollectionElement) { + this.env().subscriber.register( + this.element.subjects.get().error.subscribe(() => { + this.detectChanges(); + }), + ); + } + } + + public ngOnAddCollection() { + if (!this.elFieldsCollectionElement) { + return; + } + this.elFieldsCollectionElement.add(); + this.detectChanges(); + } + + public ngOnRemoveCollection(idx: number) { + if (!this.elFieldsCollectionElement) { + return; + } + this.elFieldsCollectionElement.remove(idx); + this.detectChanges(); + } +} +export interface SchemeEntryElement extends IlcInterface {} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/element/checkbox.ts b/application/client/src/app/ui/elements/scheme/entry/inner/element/checkbox.ts new file mode 100644 index 000000000..085594c18 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/element/checkbox.ts @@ -0,0 +1,32 @@ +import { ElementInner, FieldCategory } from './inner'; +import { Value, ValueInput } from '@platform/types/bindings'; + +export class CheckboxElement extends ElementInner { + public value: boolean; + + constructor(public defaults: boolean = false) { + super(); + this.value = defaults; + } + + public getInnerValue(): any { + return this.value; + } + + public getFieldCategory(): FieldCategory { + return FieldCategory.Inline; + } + + public setValue(value: any) { + this.value = value; + } + + public getValue(): Value { + return { Boolean: this.value }; + } +} + +export function tryFromOrigin(origin: ValueInput): CheckboxElement | undefined { + const vl = origin as { Checkbox: boolean }; + return typeof vl.Checkbox === 'boolean' ? new CheckboxElement(vl.Checkbox) : undefined; +} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/element/fields_collection/index.ts b/application/client/src/app/ui/elements/scheme/entry/inner/element/fields_collection/index.ts new file mode 100644 index 000000000..82d8131e7 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/element/fields_collection/index.ts @@ -0,0 +1,139 @@ +import { Element } from '../index'; +import { ElementInner, FieldCategory } from '../inner'; +import { StaticFieldDesc, Value, ValueInput, Field } from '@platform/types/bindings'; +import { Provider } from './provider'; +import { Subscriber, Subject } from '@platform/env/subscription'; +import { SchemeProvider } from '@ui/elements/scheme/provider'; + +class Collection extends Subscriber { + static from( + spec: StaticFieldDesc[], + uuid: string, + idx: number, + handler: () => void, + ): Collection { + const elements: Element[] = spec.map( + (field, i) => new Element(`${uuid}:${idx}:${i}`, field.interface), + ); + return new Collection(elements, handler); + } + + public checkErrors(errors: Map) { + this.dropErrors(); + errors.forEach((msg, uuid) => { + const element = this.elements.find((el) => el.uuid == uuid); + if (!element) { + return; + } + element.setError(msg); + }); + } + + constructor(public readonly elements: Element[], handler: () => void) { + super(); + this.register(...elements.map((el) => el.subjects.get().changed.subscribe(handler))); + } + + public getFields(uuid: string, idx: number): Value { + return { + Fields: this.elements.map((el, n) => { + return { id: `${uuid}:${idx}:${n}`, value: el.getValue() }; + }), + }; + } + + public dropErrors() { + this.elements.forEach((el) => el.setError(undefined)); + } +} + +export class FieldsCollectionElement extends ElementInner { + protected update() { + this.values = this.collections.map((collection, idx) => + collection.getFields(this.parent.uuid, idx), + ); + this.parent.subjects + .get() + .changed.emit({ uuid: this.parent.uuid, inner: undefined, value: this.getValue() }); + console.log(this.values); + } + + public parent!: Element; + public collections: Collection[] = []; + public values: Value[] = []; + public provider: Provider = new Provider(); + + public constructor( + public readonly spec: StaticFieldDesc[], + public readonly addTitle: string | undefined, + ) { + super(); + this.update = this.update.bind(this); + } + + public override setParentRef(parent: Element): void { + this.parent = parent; + } + + public override setProviderRef(provider: SchemeProvider): void { + this.register( + provider.subjects.get().error.subscribe((errors: Map) => { + this.collections.forEach((collection) => { + collection.checkErrors(errors); + }); + this.parent.emitError(); + }), + ); + } + + public override destroy() { + this.collections.forEach((collection) => collection.unsubscribe()); + this.unsubscribe(); + } + + public add(): void { + this.collections.push( + Collection.from(this.spec, this.parent.uuid, this.collections.length, this.update), + ); + this.parent.change(); + } + public remove(idx: number) { + this.collections.splice(idx, 1).forEach((collection) => collection.unsubscribe()); + } + + public getInnerValue(): any { + return undefined; + } + + public setValue(_value: any) { + // Do nothing. This is just a wrapper + } + + public getFieldCategory(): FieldCategory { + return FieldCategory.Row; + } + + public getValue(): Value { + return { + Fields: this.collections.map((collection, idx) => { + return { + id: `${this.parent.uuid}:${idx}`, + value: collection.getFields(this.parent.uuid, idx), + }; + }), + }; + } +} + +export function tryFromOrigin(origin: ValueInput): FieldsCollectionElement | undefined { + const vl = origin as { + FieldsCollection: { elements: StaticFieldDesc[]; add_title: string }; + }; + if (!vl.FieldsCollection) { + return undefined; + } + return new FieldsCollectionElement( + vl.FieldsCollection.elements, + vl.FieldsCollection.add_title.trim() === '' ? undefined : vl.FieldsCollection.add_title, + ); +} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/element/fields_collection/provider.ts b/application/client/src/app/ui/elements/scheme/entry/inner/element/fields_collection/provider.ts new file mode 100644 index 000000000..90dad6051 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/element/fields_collection/provider.ts @@ -0,0 +1,53 @@ +import { SchemeProvider } from '@elements/scheme/provider'; +import { Field, FieldDesc, Value } from '@platform/types/bindings'; +import { Logger } from '@env/logs'; +import { unique } from '@platform/env/sequence'; + +export class Provider extends SchemeProvider { + protected pending: string[] = []; + protected readonly logger = new Logger(`ObserveSetupProivder`); + protected readonly values: Map = new Map(); + protected fields: string[] = []; + + constructor() { + super(unique()); + } + + public override getFieldDescs(): FieldDesc[] { + return []; + } + public override load(): Promise { + return Promise.resolve(); + } + + public override setValue(uuid: string, value: Value): void { + if (!this.fields.includes(uuid)) { + this.logger.error(`Field ${uuid} doesn't belong to current provider`); + return; + } + this.values.set(uuid, value); + const fields: Field[] = []; + this.values.forEach((value, id) => { + fields.push({ id, value }); + }); + } + + public override destroy(): Promise { + this.unsubscribe(); + this.subjects.destroy(); + return Promise.resolve(); + } + + public override isValid(): boolean { + // This is dummy provider to broadcase event. No needs for validation + return true; + } + + public override getFields(): Field[] { + const fields: Field[] = []; + this.values.forEach((value, id) => { + fields.push({ id, value }); + }); + return fields; + } +} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/element/files_selector.ts b/application/client/src/app/ui/elements/scheme/entry/inner/element/files_selector.ts new file mode 100644 index 000000000..2d439bf08 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/element/files_selector.ts @@ -0,0 +1,73 @@ +import { ElementInner, FieldCategory } from './inner'; +import { Value, ValueInput } from '@platform/types/bindings'; + +export enum FilesFoldersSelectorTarget { + File, + Files, + Folder, + Folders, +} + +export class FilesFolderSelectorElement extends ElementInner { + public value: string[] = []; + + constructor( + public readonly target: FilesFoldersSelectorTarget, + public readonly exts: string[], + defaults: string | null | undefined, + ) { + super(); + if (target == FilesFoldersSelectorTarget.Folder) { + this.value = [defaults ? defaults : '']; + } + } + + public getInnerValue(): any { + return this.value; + } + + public setValue(value: any) { + this.value = value; + } + + public getFieldCategory(): FieldCategory { + return FieldCategory.Row; + } + + public getValue(): Value { + switch (this.target) { + case FilesFoldersSelectorTarget.File: + case FilesFoldersSelectorTarget.Folder: + return { File: this.value[0] == undefined ? '' : this.value[0] }; + case FilesFoldersSelectorTarget.Files: + case FilesFoldersSelectorTarget.Folders: + return { Files: this.value }; + } + } +} + +export function tryFromOrigin(origin: ValueInput): FilesFolderSelectorElement | undefined { + if (typeof origin === 'string' && origin === 'Directories') { + return new FilesFolderSelectorElement(FilesFoldersSelectorTarget.Folders, [], undefined); + } else if ((origin as { Directory: string | null }).Directory) { + return new FilesFolderSelectorElement( + FilesFoldersSelectorTarget.Folder, + [], + (origin as { Directory: string | null }).Directory, + ); + } else if ((origin as { File: string[] }).File) { + return new FilesFolderSelectorElement( + FilesFoldersSelectorTarget.File, + (origin as { File: string[] }).File, + undefined, + ); + } else if ((origin as { Files: string[] }).Files) { + return new FilesFolderSelectorElement( + FilesFoldersSelectorTarget.Files, + (origin as { Files: string[] }).Files, + undefined, + ); + } else { + return undefined; + } +} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/element/index.ts b/application/client/src/app/ui/elements/scheme/entry/inner/element/index.ts new file mode 100644 index 000000000..2cc7816fa --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/element/index.ts @@ -0,0 +1,151 @@ +import { Value, ValueInput } from '@platform/types/bindings'; +import { Subject, Subjects } from '@platform/env/subscription'; +import { ElementInner, FieldCategory } from './inner'; + +import * as CheckboxElement from './checkbox'; +import * as FilesFolderSelectorElement from './files_selector'; +import * as InputElement from './input_field'; +import * as ListElement from './list'; +import * as KeyValueElement from './key_value'; +import * as NamedValuesElement from './named'; +import * as NestedDictionaryElement from './nested_dictionary'; +import * as TimezoneSelectorElement from './timezone'; +import * as FieldsCollectionElement from './fields_collection'; +import { SchemeProvider } from '@ui/elements/scheme/provider'; + +export { CheckboxElement } from './checkbox'; +export { FilesFolderSelectorElement } from './files_selector'; +export { InputElement } from './input_field'; +export { ListElement } from './list'; +export { KeyValueElement } from './key_value'; +export { NamedValuesElement } from './named'; +export { NestedDictionaryElement, NestedDictionaryStructure } from './nested_dictionary'; +export { TimezoneSelectorElement } from './timezone'; +export { FieldsCollectionElement } from './fields_collection'; + +/** +export type ValueInput = + | { Checkbox: boolean } + | { Number: number } + | { String: [string, string] } + | { Numbers: [Array, number] } + | { Strings: [Array, string] } + | { NamedBools: Array<[string, boolean]> } + | { NamedNumbers: Array<[string, number]> } + | { NamedStrings: Array<[string, string]> } + | { KeyNumber: Map } + | { KeyNumbers: Map } + | { KeyString: Map } + | { KeyStrings: Map } + | { NestedNumbersMap: [Map>>, Map] } + | { NestedStringsMap: [Map>>, Map] } + | 'Directories' + | { Files: Array } + | { File: Array } + | 'Directory' + | 'Timezone' + | { InputsCollection: { elements: Array; add_title: string } } + | { FieldsCollection: { elements: Array; add_title: string } } + | { Bound: { output: ValueInput; inputs: Array } }; + */ +export interface ChangeEvent { + uuid: string; + inner: any; + value: Value; +} + +export class Element { + public readonly inner: ElementInner; + + public readonly subjects: Subjects<{ + changed: Subject; + loaded: Subject; + error: Subject; + }> = new Subjects({ + changed: new Subject(), + loaded: new Subject(), + error: new Subject(), + }); + + public error: string | undefined; + + constructor( + public readonly uuid: string, + protected readonly origin: ValueInput, + provider?: SchemeProvider, + ) { + let inner = undefined; + for (let el of [ + CheckboxElement, + FilesFolderSelectorElement, + InputElement, + ListElement, + KeyValueElement, + NamedValuesElement, + NestedDictionaryElement, + TimezoneSelectorElement, + FieldsCollectionElement, + ]) { + let element = el.tryFromOrigin(origin); + if (element !== undefined) { + inner = element; + break; + } + } + if (inner) { + this.inner = inner; + this.inner.setParentRef(this); + provider && this.inner.setProviderRef(provider); + } else { + throw new Error(`No controller for element ${JSON.stringify(origin)}`); + } + } + + public destroy() { + this.inner.destroy(); + this.subjects.destroy(); + } + + public loaded() { + this.subjects.get().loaded.emit(); + } + + public getFieldCategory(): FieldCategory { + return this.inner.getFieldCategory(); + } + + public change() { + this.subjects.get().changed.emit({ + uuid: this.uuid, + inner: this.inner.getInnerValue(), + value: this.inner.getValue(), + }); + } + + public setValue(value: any) { + this.inner.setValue(value); + } + + /* + | { Numbers: Array } + | { Strings: Array } + | { Directories: Array } + | { Files: Array } + | { File: string } + | { Directory: string } + | { KeyNumber: Map } + | { KeyNumbers: Map } + | { KeyString: Map } + | { KeyStrings: Map }; +*/ + public getValue(): Value { + return this.inner.getValue(); + } + + public setError(msg: string | undefined) { + this.error = msg; + } + public emitError() { + this.subjects.get().error.emit(); + } +} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/element/inner.ts b/application/client/src/app/ui/elements/scheme/entry/inner/element/inner.ts new file mode 100644 index 000000000..9da2a9da5 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/element/inner.ts @@ -0,0 +1,26 @@ +import { Value } from '@platform/types/bindings'; +import { Element } from './index'; +import { SchemeProvider } from '@ui/elements/scheme/provider'; +import { Subscriber } from '@platform/env/subscription'; + +export enum FieldCategory { + Block = 'block', + Inline = 'inline', + Row = 'row', +} +export abstract class ElementInner extends Subscriber { + public abstract getValue(): Value; + public abstract getInnerValue(): any; + public abstract setValue(value: any): void; + public abstract getFieldCategory(): FieldCategory; + public destroy(): void { + this.unsubscribe(); + // This method is optional. That's why it has default implementation. + } + public setParentRef(_parent: Element): void { + // This method is optional. That's why it has default implementation. + } + public setProviderRef(_provider: SchemeProvider): void { + // This method is optional. That's why it has default implementation. + } +} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/element/input_field.ts b/application/client/src/app/ui/elements/scheme/entry/inner/element/input_field.ts new file mode 100644 index 000000000..c674979d9 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/element/input_field.ts @@ -0,0 +1,84 @@ +import { ElementInner, FieldCategory } from './inner'; +import { Value, ValueInput } from '@platform/types/bindings'; + +enum InnerType { + String, + Number, +} + +export class InputElement extends ElementInner { + public value: T; + + constructor( + public defaults: T, + public placeholder: string, + protected readonly innerType: InnerType, + protected readonly getter: (value: T) => Value, + ) { + super(); + this.value = defaults; + } + + public getInnerValue(): any { + return this.value; + } + + public setValue(value: any) { + this.value = value; + } + + public getFieldCategory(): FieldCategory { + return FieldCategory.Inline; + } + + public getValue(): Value { + return this.getter(this.value); + } + + public isNumeric(): boolean { + return this.innerType === InnerType.Number; + } + + public isString(): boolean { + return this.innerType === InnerType.String; + } +} + +export function tryFromOrigin(origin: ValueInput): InputElement | undefined { + function as_string(origin: ValueInput): InputElement | undefined { + const vl = origin as { String: [string, string] }; + return typeof vl.String[0] === 'string' + ? new InputElement( + vl.String[0], + vl.String[1], + InnerType.String, + (value: string): Value => { + return { String: value }; + }, + ) + : undefined; + } + + function as_number(origin: ValueInput): InputElement | undefined { + const vl = origin as { Number: number }; + return typeof vl.Number === 'number' + ? new InputElement(vl.Number, '', InnerType.Number, (value: number): Value => { + return { + Number: + typeof value === 'number' + ? value + : typeof value === 'string' + ? parseInt(value, 10) + : 0, + }; + }) + : undefined; + } + if ((origin as { Number: number }).Number !== undefined) { + return as_number(origin) as InputElement; + } else if ((origin as { String: [string, string] }).String !== undefined) { + return as_string(origin) as InputElement; + } else { + return undefined; + } +} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/element/key_value.ts b/application/client/src/app/ui/elements/scheme/entry/inner/element/key_value.ts new file mode 100644 index 000000000..dc748dd41 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/element/key_value.ts @@ -0,0 +1,97 @@ +import { ElementInner, FieldCategory } from './inner'; +import { Value, ValueInput } from '@platform/types/bindings'; + +enum InnerType { + String, + Number, +} + +export class KeyValueElement extends ElementInner { + public value: V; + public items: [K, V][]; + constructor( + defaults: V, + items: [K, V][], + protected readonly innerType: InnerType, + protected readonly getter: (value: V) => Value, + ) { + super(); + this.value = defaults; + this.items = items; + } + + public getInnerValue(): any { + return this.value; + } + + public setValue(value: any) { + this.value = value; + } + + public getFieldCategory(): FieldCategory { + return FieldCategory.Inline; + } + + public getValue(): Value { + return this.getter(this.value); + } + public isNumeric(): boolean { + return this.innerType === InnerType.Number; + } + + public isString(): boolean { + return this.innerType === InnerType.String; + } +} + +export function tryFromOrigin(origin: ValueInput): KeyValueElement | undefined { + function as_named_numbers(origin: ValueInput): KeyValueElement | undefined { + const vl = origin as { NamedNumbers: [[string, number][], number] }; + return vl.NamedNumbers + ? new KeyValueElement( + vl.NamedNumbers[1], + vl.NamedNumbers[0], + InnerType.Number, + (value: number): Value => { + return { + Number: + typeof value === 'number' + ? value + : typeof value === 'string' + ? parseInt(value, 10) + : 0, + }; + }, + ) + : undefined; + } + function as_named_strings(origin: ValueInput): KeyValueElement | undefined { + const vl = origin as { NamedStrings: [[string, string][], string] }; + return vl.NamedStrings + ? new KeyValueElement( + vl.NamedStrings[1], + vl.NamedStrings[0], + InnerType.String, + (value: unknown): Value => { + return { + String: + typeof value === 'string' + ? value + : value + ? typeof value.toString === 'function' + ? value.toString() + : '' + : '', + }; + }, + ) + : undefined; + } + if ((origin as { NamedNumbers: [[string, number][], number] }).NamedNumbers) { + return as_named_numbers(origin) as KeyValueElement; + } else if ((origin as { NamedStrings: [[string, string][], string] }).NamedStrings) { + return as_named_strings(origin) as KeyValueElement; + } else { + return undefined; + } +} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/element/list.ts b/application/client/src/app/ui/elements/scheme/entry/inner/element/list.ts new file mode 100644 index 000000000..36a8e78c9 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/element/list.ts @@ -0,0 +1,90 @@ +import { ElementInner, FieldCategory } from './inner'; +import { Value, ValueInput } from '@platform/types/bindings'; + +enum InnerType { + String, + Number, +} + +export class ListElement extends ElementInner { + public value: T; + public items: T[]; + constructor( + defaults: T, + items: T[], + protected readonly innerType: InnerType, + protected readonly getter: (value: T) => Value, + ) { + super(); + this.value = defaults; + this.items = items; + } + + public getInnerValue(): any { + return this.value; + } + + public setValue(value: any) { + this.value = value; + } + + public getFieldCategory(): FieldCategory { + return FieldCategory.Inline; + } + + public getValue(): Value { + return this.getter(this.value); + } + public isNumeric(): boolean { + return this.innerType === InnerType.Number; + } + + public isString(): boolean { + return this.innerType === InnerType.String; + } +} + +export function tryFromOrigin(origin: ValueInput): ListElement | undefined { + function as_numbers_list(origin: ValueInput): ListElement | undefined { + const vl = origin as { Numbers: [Array, number] }; + return vl.Numbers + ? new ListElement( + vl.Numbers[1], + vl.Numbers[0], + InnerType.Number, + (value: number): Value => { + return { + Number: + typeof value === 'number' + ? value + : typeof value === 'string' + ? parseInt(value, 10) + : 0, + }; + }, + ) + : undefined; + } + function as_string_list(origin: ValueInput): ListElement | undefined { + const vl = origin as { Strings: [Array, string] }; + return vl.Strings + ? new ListElement( + vl.Strings[1], + vl.Strings[0], + InnerType.String, + (value: string): Value => { + return { + String: value, + }; + }, + ) + : undefined; + } + if ((origin as { Numbers: [Array, number] }).Numbers) { + return as_numbers_list(origin) as ListElement; + } else if ((origin as { Strings: [Array, string] }).Strings) { + return as_string_list(origin) as ListElement; + } else { + return undefined; + } +} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/element/named.ts b/application/client/src/app/ui/elements/scheme/entry/inner/element/named.ts new file mode 100644 index 000000000..cb9d8eb4e --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/element/named.ts @@ -0,0 +1,51 @@ +import { ElementInner, FieldCategory } from './inner'; +import { Value, ValueInput } from '@platform/types/bindings'; + +export class NamedValuesElement extends ElementInner { + public value: T; + public items: { key: string; value: T }[]; + constructor( + defaults: T, + items: { key: string; value: T }[], + protected readonly getter: (value: T) => Value, + ) { + super(); + this.value = defaults; + this.items = items; + } + + public getInnerValue(): any { + return this.value; + } + + public setValue(value: any) { + this.value = value; + } + + public getFieldCategory(): FieldCategory { + return FieldCategory.Inline; + } + + public getValue(): Value { + return this.getter(this.value); + } +} + +export function tryFromOrigin(origin: ValueInput): NamedValuesElement | undefined { + const vl = origin as { NamedBools: Array<[string, boolean]> }; + return vl.NamedBools instanceof Array + ? vl.NamedBools.length > 0 + ? new NamedValuesElement( + vl.NamedBools[0][1], + vl.NamedBools.map((item) => { + return { key: item[0], value: item[1] }; + }), + (value: boolean): Value => { + return { + Boolean: value, + }; + }, + ) + : undefined + : undefined; +} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/element/nested_dictionary.ts b/application/client/src/app/ui/elements/scheme/entry/inner/element/nested_dictionary.ts new file mode 100644 index 000000000..0d7809ce4 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/element/nested_dictionary.ts @@ -0,0 +1,51 @@ +import { ElementInner, FieldCategory } from './inner'; +import { Value, ValueInput } from '@platform/types/bindings'; + +export type NestedDictionaryStructure = Map>>; + +export class NestedDictionaryElement extends ElementInner { + public value: V; + constructor( + public defaults: V, + public readonly items: NestedDictionaryStructure, + public readonly dictionary: Map, + protected readonly getter: (value: V) => Value, + ) { + super(); + this.value = defaults; + } + + public getInnerValue(): any { + return this.value; + } + + public setValue(value: any) { + this.value = value; + } + + public getFieldCategory(): FieldCategory { + return FieldCategory.Block; + } + + public getValue(): Value { + return this.getter(this.value); + } +} + +export function tryFromOrigin( + origin: ValueInput, +): NestedDictionaryElement> | undefined { + const vl = origin as { + NestedNumbersMap: [Map>>, Map]; + }; + return vl.NestedNumbersMap instanceof Array && vl.NestedNumbersMap.length === 2 + ? new NestedDictionaryElement>( + new Map(), + vl.NestedNumbersMap[0], + vl.NestedNumbersMap[1], + (selections: Map) => { + return { KeyStrings: selections }; + }, + ) + : undefined; +} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/element/timezone.ts b/application/client/src/app/ui/elements/scheme/entry/inner/element/timezone.ts new file mode 100644 index 000000000..2984efd0b --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/element/timezone.ts @@ -0,0 +1,30 @@ +import { ElementInner, FieldCategory } from './inner'; +import { Value, ValueInput } from '@platform/types/bindings'; + +export class TimezoneSelectorElement extends ElementInner { + public value: number = -1; + + public getInnerValue(): any { + return this.value; + } + + public setValue(value: any) { + this.value = value; + } + + public getFieldCategory(): FieldCategory { + return FieldCategory.Row; + } + + public getValue(): Value { + return { Number: this.value }; + } +} + +export function tryFromOrigin(origin: ValueInput): TimezoneSelectorElement | undefined { + if (typeof origin === 'string' && origin === 'Timezone') { + return new TimezoneSelectorElement(); + } else { + return undefined; + } +} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/module.ts b/application/client/src/app/ui/elements/scheme/entry/inner/module.ts new file mode 100644 index 000000000..b46bf8815 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/module.ts @@ -0,0 +1,35 @@ +// import { NgModule } from '@angular/core'; +// import { CommonModule } from '@angular/common'; +// import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +// import { MatCardModule } from '@angular/material/card'; +// import { MatDividerModule } from '@angular/material/divider'; +// import { MatFormFieldModule } from '@angular/material/form-field'; +// import { MatInputModule } from '@angular/material/input'; +// import { MatCheckboxModule } from '@angular/material/checkbox'; +// import { MatButtonModule } from '@angular/material/button'; +// import { MatSelectModule } from '@angular/material/select'; +// import { MatProgressBarModule } from '@angular/material/progress-bar'; + +// import { SchemeEntryElement } from './component'; +// import { ComplexFieldsModule } from './complex/module'; + +// @NgModule({ +// imports: [ +// CommonModule, +// FormsModule, +// ReactiveFormsModule, +// MatCheckboxModule, +// MatCardModule, +// MatDividerModule, +// MatFormFieldModule, +// MatInputModule, +// MatButtonModule, +// MatSelectModule, +// MatProgressBarModule, +// ComplexFieldsModule, +// ], +// declarations: [SchemeEntryElement], +// exports: [SchemeEntryElement], +// bootstrap: [], +// }) +// export class InnerModule {} diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/styles.less b/application/client/src/app/ui/elements/scheme/entry/inner/styles.less new file mode 100644 index 000000000..7c7437830 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/styles.less @@ -0,0 +1,38 @@ +@import '../../../../styles/variables.less'; + + +:host { + position: relative; + display: block; + & div.collections { + position: relative; + display: flex; + justify-content: center; + flex-direction: column; + & div.collection { + position: relative; + display: flex; + flex-direction: row; + justify-content: flex-start; + & div.element { + margin-right: 12px; + & app-settings-scheme-element { + height: 42px; + } + } + } + & button { + color: var(--scheme-color-1) + } + & div.controls { + position: relative; + width: 100%; + display: flex; + + } + } + & p.error { + color: var(--scheme-color-error-light); + } + +} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/scheme/entry/inner/template.html b/application/client/src/app/ui/elements/scheme/entry/inner/template.html new file mode 100644 index 000000000..d05317007 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/inner/template.html @@ -0,0 +1,86 @@ + + + + + {{item}} + + + + + + + {{item[0]}} + + + + + + + {{item.key}} + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +

{{element.error}}

+
+ +
+
+ + +
+
diff --git a/application/client/src/app/ui/elements/scheme/entry/styles.less b/application/client/src/app/ui/elements/scheme/entry/styles.less new file mode 100644 index 000000000..a2f6362e5 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/styles.less @@ -0,0 +1,38 @@ +@import '../../../styles/variables.less'; + + +:host { + position: relative; + padding: 2px; + margin: 6px; + &.field-inline { + display: inline-block; + height: 62px; + } + &.field-row { + display: block; + } + &.field-block { + display: block; + width: auto; + height: auto; + } + p.title { + display: block; + color: var(--scheme-color-1); + padding: 3px 0px 0 16px; + } + p.desc { + display: block; + color: var(--scheme-color-2); + padding: 3px 0px 0 16px; + } + p.error { + color: var(--scheme-color-error-light); + } + div.loading { + position: relative; + height: 2px; + width: 100%; + } +} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/scheme/entry/template.html b/application/client/src/app/ui/elements/scheme/entry/template.html new file mode 100644 index 000000000..2f40d6eca --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/entry/template.html @@ -0,0 +1,12 @@ +

{{name}}

+

{{desc}}

+

{{error}}

+
+

Loading...

+ +
+ diff --git a/application/client/src/app/ui/elements/scheme/field.ts b/application/client/src/app/ui/elements/scheme/field.ts new file mode 100644 index 000000000..19d2552c8 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/field.ts @@ -0,0 +1,30 @@ +import { FieldDesc, LazyFieldDesc, StaticFieldDesc } from '@platform/types/bindings'; + +export class WrappedField { + public id: string; + public name: string; + public desc: string; + public binding: string | undefined; + public pending: LazyFieldDesc | undefined; + public loaded: StaticFieldDesc | undefined; + + constructor(field: FieldDesc) { + const pending = (field as { Lazy: LazyFieldDesc }).Lazy; + const loaded = (field as { Static: StaticFieldDesc }).Static; + this.pending = pending; + this.loaded = loaded; + if (pending) { + this.id = pending.id; + this.desc = pending.desc; + this.name = pending.name; + this.binding = !pending.binding ? undefined : pending.binding; + } else if (loaded) { + this.id = loaded.id; + this.desc = loaded.desc; + this.name = loaded.name; + this.binding = !loaded.binding ? undefined : loaded.binding; + } else { + throw new Error(`Invalid FieldDesc: ${JSON.stringify(field)}`); + } + } +} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/tcp/module.ts b/application/client/src/app/ui/elements/scheme/module.ts similarity index 72% rename from application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/tcp/module.ts rename to application/client/src/app/ui/elements/scheme/module.ts index d25b0c9b9..0d6102c6b 100644 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/tcp/module.ts +++ b/application/client/src/app/ui/elements/scheme/module.ts @@ -1,32 +1,35 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatDividerModule } from '@angular/material/divider'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatIconModule } from '@angular/material/icon'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; import { MatInputModule } from '@angular/material/input'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSelectModule } from '@angular/material/select'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { QuickSetup } from './component'; +import { SettingsScheme } from './component'; +import { SchemeEntry } from './entry/component'; @NgModule({ imports: [ CommonModule, FormsModule, ReactiveFormsModule, - MatButtonModule, + MatCheckboxModule, MatCardModule, MatDividerModule, - MatProgressBarModule, - MatIconModule, MatFormFieldModule, - MatSelectModule, MatInputModule, + MatButtonModule, + MatSelectModule, + MatProgressBarModule, + SchemeEntry, ], - declarations: [QuickSetup], - exports: [QuickSetup] + declarations: [SettingsScheme], + exports: [SettingsScheme], + bootstrap: [], }) -export class QuickSetupModule {} +export class SettingsSchemeModule {} diff --git a/application/client/src/app/ui/elements/scheme/provider.ts b/application/client/src/app/ui/elements/scheme/provider.ts new file mode 100644 index 000000000..bae0b4619 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/provider.ts @@ -0,0 +1,32 @@ +import { Field, FieldDesc, StaticFieldDesc, Value } from '@platform/types/bindings'; +import { Subscriber, Subject, Subjects } from '@platform/env/subscription'; +import { unique } from '@platform/env/sequence'; + +export interface ForcedValueChanges { + target: string; + value: any; +} +export abstract class SchemeProvider extends Subscriber { + public subjects: Subjects<{ + loaded: Subject; + forced: Subject; + error: Subject>; + }> = new Subjects({ + loaded: new Subject(), + forced: new Subject(), + error: new Subject>(), + }); + + constructor(public readonly uuid: string) { + super(); + } + public abstract getFieldDescs(): FieldDesc[]; + public abstract load(): Promise; + public abstract destroy(): Promise; + public abstract setValue(uuid: string, value: Value): void; + public abstract getFields(): Field[]; + public abstract isValid(): boolean; + public force(target: string, value: any) { + this.subjects.get().forced.emit({ target, value }); + } +} diff --git a/application/client/src/app/ui/elements/scheme/styles.less b/application/client/src/app/ui/elements/scheme/styles.less new file mode 100644 index 000000000..d34de6b5f --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/styles.less @@ -0,0 +1,7 @@ +@import '../../styles/variables.less'; + + +:host { + + +} \ No newline at end of file diff --git a/application/client/src/app/ui/elements/scheme/template.html b/application/client/src/app/ui/elements/scheme/template.html new file mode 100644 index 000000000..afe26d0a1 --- /dev/null +++ b/application/client/src/app/ui/elements/scheme/template.html @@ -0,0 +1,8 @@ + +

+ This component doesn't have any settings or options. +

diff --git a/application/client/src/app/ui/elements/tree/component.ts b/application/client/src/app/ui/elements/tree/component.ts index c6c3b1ae8..021415d72 100644 --- a/application/client/src/app/ui/elements/tree/component.ts +++ b/application/client/src/app/ui/elements/tree/component.ts @@ -13,9 +13,9 @@ import { ChangesDetector } from '@ui/env/extentions/changes'; import { State } from './state'; import { Initial } from '@env/decorators/initial'; import { stop } from '@ui/env/dom'; +import { SessionOrigin } from '@service/session/origin'; import * as Scheme from './scheme'; -import * as Factory from '@platform/types/observe/factory'; @Component({ selector: 'app-elements-tree', @@ -35,10 +35,7 @@ export class ElementsTreeSelector public state: State; private init!: Promise; - constructor( - cdRef: ChangeDetectorRef, - private _sanitizer: DomSanitizer, - ) { + constructor(cdRef: ChangeDetectorRef, private _sanitizer: DomSanitizer) { super(cdRef); this.state = new State(this); } @@ -113,13 +110,7 @@ export class ElementsTreeSelector handler: () => { this.ilc() .services.system.session.initialize() - .observe( - new Factory.File() - .type(Factory.FileType.Text) - .asText() - .file(entity.getPath()) - .get(), - ) + .observe(SessionOrigin.file(entity.getPath())) .catch((err: Error) => { this.log().error(`Fail to open text file; error: ${err.message}`); }); @@ -130,13 +121,7 @@ export class ElementsTreeSelector handler: () => { this.ilc() .services.system.session.initialize() - .observe( - new Factory.File() - .asDlt() - .type(Factory.FileType.Binary) - .file(entity.getPath()) - .get(), - ) + .observe(SessionOrigin.file(entity.getPath())) .catch((err: Error) => { this.log().error(`Fail to open dlt file; error: ${err.message}`); }); @@ -147,13 +132,7 @@ export class ElementsTreeSelector handler: () => { this.ilc() .services.system.session.initialize() - .observe( - new Factory.File() - .type(Factory.FileType.PcapNG) - .asDlt() - .file(entity.getPath()) - .get(), - ) + .observe(SessionOrigin.file(entity.getPath())) .catch((err: Error) => { this.log().error(`Fail to open pcapng file; error: ${err.message}`); }); @@ -164,13 +143,7 @@ export class ElementsTreeSelector handler: () => { this.ilc() .services.system.session.initialize() - .observe( - new Factory.File() - .type(Factory.FileType.PcapLegacy) - .asDlt() - .file(entity.getPath()) - .get(), - ) + .observe(SessionOrigin.file(entity.getPath())) .catch((err: Error) => { this.log().error(`Fail to open pcapng file; error: ${err.message}`); }); @@ -181,13 +154,7 @@ export class ElementsTreeSelector handler: () => { this.ilc() .services.system.session.initialize() - .observe( - new Factory.File() - .type(Factory.FileType.ParserPlugin) - .asParserPlugin() - .file(entity.getPath()) - .get(), - ) + .observe(SessionOrigin.file(entity.getPath())) .catch((err: Error) => { this.log().error( `Fail to open file with plugins: errord ${err.message}`, diff --git a/application/client/src/app/ui/elements/tree/module.ts b/application/client/src/app/ui/elements/tree/module.ts index 68e9df68c..d85253f6b 100644 --- a/application/client/src/app/ui/elements/tree/module.ts +++ b/application/client/src/app/ui/elements/tree/module.ts @@ -5,7 +5,6 @@ import { CdkTreeModule } from '@angular/cdk/tree'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { FilterInputModule } from '@elements/filter/module'; import { ElementsTreeSelector } from './component'; @@ -21,7 +20,6 @@ import { InputListenerDirective } from '@ui/env/directives/input'; MatButtonModule, MatIconModule, FilterInputModule, - MatProgressSpinnerModule, ], declarations: [ElementsTreeSelector, InputListenerDirective], exports: [ElementsTreeSelector], diff --git a/application/client/src/app/ui/env/directives/dragdrop.file.ts b/application/client/src/app/ui/env/directives/dragdrop.file.ts index 6542a4586..bb528ff5b 100644 --- a/application/client/src/app/ui/env/directives/dragdrop.file.ts +++ b/application/client/src/app/ui/env/directives/dragdrop.file.ts @@ -63,7 +63,7 @@ export class MatDragDropFileFeatureDirective implements OnDestroy { constructor(protected readonly element: ElementRef) { // Note: The files property of DataTransfer objects can only be accessed from within - // the drop event. For all other events, the files property will be empty — because + // the drop event. For all other events, the files property will be empty - because // its underlying data store will be in a protected mode. // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/files // diff --git a/application/client/src/app/ui/env/extentions/changes.ts b/application/client/src/app/ui/env/extentions/changes.ts index a3ce43ec6..e065a9ff2 100644 --- a/application/client/src/app/ui/env/extentions/changes.ts +++ b/application/client/src/app/ui/env/extentions/changes.ts @@ -30,20 +30,35 @@ export class ChangesDetector { if (this._detauched) { return; } - this._changeDetectorRef.forEach((cdRef) => cdRef.detectChanges()); + this._changeDetectorRef.forEach((cdRef) => { + if (!cdRef) { + console.warn(`[detectChanges] Detected undefined cdRef in ChangesDetector`); + } + cdRef && cdRef.detectChanges(); + }); } public markChangesForCheck() { if (this._detauched) { return; } - this._changeDetectorRef.forEach((cdRef) => cdRef.markForCheck()); + this._changeDetectorRef.forEach((cdRef) => { + if (!cdRef) { + console.warn(`[markForCheck] Detected undefined cdRef in ChangesDetector`); + } + cdRef && cdRef.markForCheck(); + }); } public reattachChangesDetector() { if (this._detauched) { return; } - this._changeDetectorRef.forEach((cdRef) => cdRef.reattach()); + this._changeDetectorRef.forEach((cdRef) => { + if (!cdRef) { + console.warn(`[reattach] Detected undefined cdRef in ChangesDetector`); + } + cdRef && cdRef.reattach(); + }); } } diff --git a/application/client/src/app/ui/layout/popups/popup/component.ts b/application/client/src/app/ui/layout/popups/popup/component.ts index 7924da041..bc5de99e0 100644 --- a/application/client/src/app/ui/layout/popups/popup/component.ts +++ b/application/client/src/app/ui/layout/popups/popup/component.ts @@ -24,6 +24,9 @@ export class LayoutPopup extends ChangesDetector implements AfterViewInit, After @Input() public popup!: Popup; @Input() public close!: () => void; + public width: string = 'auto'; + public height: string = 'auto'; + @HostBinding('class') get cssClassEdited() { if (this.popup.options.position === undefined) { return `v-bottom h-center`; @@ -55,6 +58,26 @@ export class LayoutPopup extends ChangesDetector implements AfterViewInit, After : this.popup.options.component.inputs; this.popup.options.component.inputs.close = this.close; this.popup.options.component.inputs.popup = this.popup; + const options = this.popup.options; + if (!options) { + return; + } + if (options.size && options.size.width) { + const abs = options.size.width <= 1; + this.width = `${abs ? options.size.width * 100 : options.size.width}${ + abs ? '%' : 'px' + }`; + } else { + this.width = 'auto'; + } + if (options.size && options.size.height) { + const abs = options.size.height <= 1; + this.height = `${abs ? options.size.height * 100 : options.size.height}${ + abs ? '%' : 'px' + }`; + } else { + this.height = 'auto'; + } } public ngAfterViewInit(): void { @@ -83,12 +106,21 @@ export class LayoutPopup extends ChangesDetector implements AfterViewInit, After ); } - public ngStyle(): { width: string } { - if (this.popup.options === undefined || this.popup.options.width === undefined) { - return { width: 'auto' }; - } else { - return { width: `${this.popup.options.width}px` }; - } + public ngStyle(): { + width: string; + height: string; + maxWidth: string; + maxHeight: string; + position: string; + } { + return { + width: this.width, + height: this.height, + maxWidth: this.width.endsWith('%') ? 'none' : '70%', + maxHeight: this.height.endsWith('%') ? 'none' : '80%', + position: + this.width.endsWith('%') || this.height.endsWith('%') ? 'absolute' : 'relative', + }; } } export interface LayoutPopup extends IlcInterface {} diff --git a/application/client/src/app/ui/layout/popups/popup/styles.less b/application/client/src/app/ui/layout/popups/popup/styles.less index bc2c34659..740ad7d26 100644 --- a/application/client/src/app/ui/layout/popups/popup/styles.less +++ b/application/client/src/app/ui/layout/popups/popup/styles.less @@ -12,8 +12,6 @@ & .container { background: var(--scheme-color-5); box-shadow: 3px 3px 4px rgba(0,0,0,0.4); - max-width: 70%; - max-height: 80%; overflow-x: hidden; overflow-y: auto; } diff --git a/application/client/src/app/ui/service/popup/popup.ts b/application/client/src/app/ui/service/popup/popup.ts index 1a34b84c2..7539da408 100644 --- a/application/client/src/app/ui/service/popup/popup.ts +++ b/application/client/src/app/ui/service/popup/popup.ts @@ -21,7 +21,12 @@ export interface Position { export interface PopupOptions { closable?: boolean; - width?: number; + size?: { + // If >=1 consider as %; if 1< consider as px. + width?: number; + // If >=1 consider as %; if 1< consider as px. + height?: number; + }; position?: Position; closeOnKey?: string; closeOnBGClick?: boolean; diff --git a/application/client/src/app/ui/service/toolbar.ts b/application/client/src/app/ui/service/toolbar.ts index fb980a0e8..741ac28ca 100644 --- a/application/client/src/app/ui/service/toolbar.ts +++ b/application/client/src/app/ui/service/toolbar.ts @@ -10,7 +10,6 @@ import { Tabs } from './toolbar/tabs'; @SetupService(ui['toolbar']) export class Service extends Implementation { - private _emitter!: Emitter; private _channel!: Channel; private _services!: Services; private _available!: Tabs; @@ -18,10 +17,8 @@ export class Service extends Implementation { private _sessions: Map = new Map(); public override ready(): Promise { - this._emitter = ilc.emitter(this.getName(), this.log()); this._channel = ilc.channel(this.getName(), this.log()); this._services = ilc.services(this.getName(), this.log()); - this._channel.ux.hotkey(this._onHotKey.bind(this)); this._channel.session.change(this._onSessionChange.bind(this)); this._channel.session.closed(this._onSessionClosed.bind(this)); this._channel.ui.toolbar.view(this._onViewChange.bind(this)); @@ -137,19 +134,6 @@ export class Service extends Implementation { }); } - private _onHotKey(event: Declarations.HotkeyEvent) { - console.log(`Not implemented: ${event}`); - // if (this._active === undefined) { - // return; - // } - // const service: TabsService | undefined = this._sessions.get(this._active); - // if (service === undefined) { - // return; - // } - // LayoutStateService.toolbarMax(); - // service.setActive(UUIDs.search); - } - private _onSessionClosed(session: string) { const service: TabsService | undefined = this._sessions.get(session); if (service === undefined) { diff --git a/application/client/src/app/ui/styles/containers.less b/application/client/src/app/ui/styles/containers.less index e90cc44b8..a738f879d 100644 --- a/application/client/src/app/ui/styles/containers.less +++ b/application/client/src/app/ui/styles/containers.less @@ -36,6 +36,10 @@ div.preset-container{ overflow: hidden; justify-content: stretch; justify-items: stretch; + &.vertical-scrolling { + overflow-y: auto; + overflow-x: hidden; + } } & div.preset-container-right { position: absolute; diff --git a/application/client/src/app/ui/styles/material.less b/application/client/src/app/ui/styles/material.less index 47fb5a0a2..bc1f72960 100644 --- a/application/client/src/app/ui/styles/material.less +++ b/application/client/src/app/ui/styles/material.less @@ -1,5 +1,11 @@ @import './variables.less'; - +*.mat-form-disable-background { + & mat-form-field { + & .mdc-text-field--filled:not(.mdc-text-field--disabled) { + background-color: unset; + } + } +} mat-form-field.material-mofication-normal { &.stretch { display: flex; diff --git a/application/client/src/app/ui/tabs/module.ts b/application/client/src/app/ui/tabs/module.ts index b723638d6..a3c6f4adf 100644 --- a/application/client/src/app/ui/tabs/module.ts +++ b/application/client/src/app/ui/tabs/module.ts @@ -1,24 +1,24 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { ObserveModule } from '@ui/tabs/observe/module'; import { MultipleFilesModule } from '@ui/tabs/multiplefiles/module'; import { SettingsModule } from '@ui/tabs/settings/module'; import { ChangelogModule } from '@ui/tabs/changelogs/module'; import { HelpModule } from '@ui/tabs/help/module'; import { PluginsManagerModule } from '@ui/tabs/plugins/module'; +import { SetupModule } from '@ui/tabs/setup/module'; @NgModule({ imports: [ CommonModule, - ObserveModule, MultipleFilesModule, SettingsModule, ChangelogModule, HelpModule, PluginsManagerModule, + SetupModule, ], declarations: [], - exports: [ObserveModule, MultipleFilesModule, SettingsModule, ChangelogModule, HelpModule], + exports: [MultipleFilesModule, SettingsModule, ChangelogModule, HelpModule], bootstrap: [], }) export class TabsModule {} diff --git a/application/client/src/app/ui/tabs/multiplefiles/file.holder.ts b/application/client/src/app/ui/tabs/multiplefiles/file.holder.ts index a13a9d716..c092edad9 100644 --- a/application/client/src/app/ui/tabs/multiplefiles/file.holder.ts +++ b/application/client/src/app/ui/tabs/multiplefiles/file.holder.ts @@ -1,7 +1,7 @@ import { Matchee } from '@module/matcher'; import { bytesToStr } from '@env/str'; import { File } from '@platform/types/files'; -import { FileType } from '@platform/types/observe/types/file'; +import { FileType } from '@platform/types/files'; import * as wasm from '@loader/wasm'; diff --git a/application/client/src/app/ui/tabs/multiplefiles/state.ts b/application/client/src/app/ui/tabs/multiplefiles/state.ts index 83a4b5042..dfdca61d0 100644 --- a/application/client/src/app/ui/tabs/multiplefiles/state.ts +++ b/application/client/src/app/ui/tabs/multiplefiles/state.ts @@ -1,7 +1,7 @@ import { FileHolder } from './file.holder'; import { bytesToStr } from '@env/str'; import { File } from '@platform/types/files'; -import { FileType } from '@platform/types/observe/types/file'; +import { FileType } from '@platform/types/files'; import { Subject } from '@platform/env/subscription'; import { EEventType, IEvent } from './structure/component'; import { Holder } from '@module/matcher'; @@ -12,7 +12,7 @@ import { getUniqueColorTo } from '@ui/styles/colors'; import { Sort } from '@angular/material/sort'; import { ChangesDetector } from '@ui/env/extentions/changes'; -import * as Factory from '@platform/types/observe/factory'; +import { SessionOrigin } from '@service/session/origin'; export interface IMultifile { usedColors: string[]; @@ -147,57 +147,27 @@ export class State extends Holder { return this._ref .ilc() .services.system.session.initialize() - .observe( - new Factory.Concat() - .asText() - .type(Factory.FileType.Text) - .files(files) - .get(), - ); + .observe(SessionOrigin.files(files)); case FileType.PcapNG: return this._ref .ilc() .services.system.session.initialize() - .configure( - new Factory.Concat() - .asDlt() - .type(Factory.FileType.PcapNG) - .files(files) - .get(), - ); + .configure(SessionOrigin.files(files)); case FileType.PcapLegacy: return this._ref .ilc() .services.system.session.initialize() - .configure( - new Factory.Concat() - .asDlt() - .type(Factory.FileType.PcapLegacy) - .files(files) - .get(), - ); + .configure(SessionOrigin.files(files)); case FileType.Binary: return this._ref .ilc() .services.system.session.initialize() - .configure( - new Factory.Concat() - .asDlt() - .type(Factory.FileType.Binary) - .files(files) - .get(), - ); + .configure(SessionOrigin.files(files)); case FileType.ParserPlugin: return this._ref .ilc() .services.system.session.initialize() - .configure( - new Factory.Concat() - .asParserPlugin() - .type(Factory.FileType.ParserPlugin) - .files(files) - .get(), - ); + .configure(SessionOrigin.files(files)); default: return Promise.reject( new Error(`Unsupported type ${this.files[0].type}`), @@ -230,13 +200,7 @@ export class State extends Holder { this._ref .ilc() .services.system.session.initialize() - .observe( - new Factory.File() - .asText() - .type(Factory.FileType.Text) - .file(file.filename) - .get(), - ) + .observe(SessionOrigin.file(file.filename)) .catch((err: Error) => { this._ref .ilc() @@ -249,13 +213,7 @@ export class State extends Holder { this._ref .ilc() .services.system.session.initialize() - .configure( - new Factory.File() - .asDlt() - .type(Factory.FileType.Binary) - .file(file.filename) - .get(), - ) + .configure(SessionOrigin.file(file.filename)) .catch((err: Error) => { this._ref .ilc() @@ -268,13 +226,7 @@ export class State extends Holder { this._ref .ilc() .services.system.session.initialize() - .configure( - new Factory.File() - .asDlt() - .type(Factory.FileType.PcapNG) - .file(file.filename) - .get(), - ) + .configure(SessionOrigin.file(file.filename)) .catch((err: Error) => { this._ref .ilc() @@ -287,13 +239,7 @@ export class State extends Holder { this._ref .ilc() .services.system.session.initialize() - .configure( - new Factory.File() - .asDlt() - .type(Factory.FileType.PcapLegacy) - .file(file.filename) - .get(), - ) + .configure(SessionOrigin.file(file.filename)) .catch((err: Error) => { this._ref .ilc() @@ -306,13 +252,7 @@ export class State extends Holder { this._ref .ilc() .services.system.session.initialize() - .configure( - new Factory.File() - .asParserPlugin() - .type(Factory.FileType.ParserPlugin) - .file(file.filename) - .get(), - ) + .configure(SessionOrigin.file(file.filename)) .catch((err: Error) => { this._ref .ilc() diff --git a/application/client/src/app/ui/tabs/multiplefiles/structure/component.ts b/application/client/src/app/ui/tabs/multiplefiles/structure/component.ts index 14e6f10b0..7493ad8b1 100644 --- a/application/client/src/app/ui/tabs/multiplefiles/structure/component.ts +++ b/application/client/src/app/ui/tabs/multiplefiles/structure/component.ts @@ -11,7 +11,7 @@ import { Ilc, IlcInterface } from '@env/decorators/component'; import { MatSort, Sort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; import { FileHolder } from '../file.holder'; -import { FileType } from '@platform/types/observe/types/file'; +import { FileType } from '@platform/types/files'; import { Subscription } from 'rxjs'; import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { State } from '../state'; diff --git a/application/client/src/app/ui/tabs/observe/action.ts b/application/client/src/app/ui/tabs/observe/action.ts deleted file mode 100644 index a4beea823..000000000 --- a/application/client/src/app/ui/tabs/observe/action.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Subject, Subjects } from '@platform/env/subscription'; - -const DEFAULT_CAPTION = 'Run'; -const DEFAULT_STATE = false; - -export class Action { - public subjects: Subjects<{ - updated: Subject; - apply: Subject; - applied: Subject; - }> = new Subjects({ - updated: new Subject(), - apply: new Subject(), - applied: new Subject(), - }); - - public caption: string = DEFAULT_CAPTION; - public disabled: boolean = DEFAULT_STATE; - - public destroy(): void { - this.subjects.destroy(); - } - - public setCaption(caption: string): void { - this.caption = caption; - this.subjects.get().updated.emit(); - } - - public setDisabled(disabled: boolean): void { - if (this.disabled === disabled) { - return; - } - this.disabled = disabled; - this.subjects.get().updated.emit(); - } - - public defaults(): void { - this.caption = DEFAULT_CAPTION; - this.disabled = DEFAULT_STATE; - this.subjects.get().updated.emit(); - } - - public apply(): void { - this.subjects.get().apply.emit(); - } - - public applied(): void { - this.subjects.get().applied.emit(); - } -} diff --git a/application/client/src/app/ui/tabs/observe/component.ts b/application/client/src/app/ui/tabs/observe/component.ts deleted file mode 100644 index 813e86434..000000000 --- a/application/client/src/app/ui/tabs/observe/component.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - Component, - ChangeDetectorRef, - ChangeDetectionStrategy, - Input, - AfterContentInit, - OnDestroy, -} from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { State, IApi, IInputs } from './state'; -import { Observe } from '@platform/types/observe'; - -@Component({ - selector: 'app-tabs-observe', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: false, -}) -@Initial() -@Ilc() -export class TabObserve extends ChangesDetector implements AfterContentInit, OnDestroy { - // This method is used only to highlight inputs of component - static inputs(inputs: IInputs): IInputs { - return inputs; - } - - @Input() observe!: Observe; - @Input() api!: IApi; - - public state!: State; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngOnDestroy(): void { - this.state.destroy(); - } - - public ngAfterContentInit(): void { - this.state = new State(this, this.observe); - } -} -export interface TabObserve extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/component.ts b/application/client/src/app/ui/tabs/observe/config-schema/component.ts deleted file mode 100644 index e7340e329..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/component.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Component, ChangeDetectorRef, Input, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { State } from './state'; -import { State as ParserState } from '../parsers/general/plugin/state'; - -@Component({ - selector: 'app-tab-config-schemas', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class ConfigSchemas extends ChangesDetector implements AfterContentInit { - @Input() parserState!: ParserState; - - public readonly state: State = new State(); - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - this.env().subscriber.register( - this.parserState.selected.subscribe(() => { - this.state.reload(this.parserState); - }), - ); - } -} - -export interface ConfigSchemas extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/entry/component.ts b/application/client/src/app/ui/tabs/observe/config-schema/entry/component.ts deleted file mode 100644 index 4719e84c4..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/entry/component.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Component, ChangeDetectorRef, Input } from '@angular/core'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { PluginConfigSchemaItem } from '@platform/types/bindings/plugins'; -import { State } from '../state'; - -@Component({ - selector: 'app-tabs-config-schema-entry', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -export class ConfgiSchemaEntry extends ChangesDetector { - @Input() public config!: PluginConfigSchemaItem; - @Input() public state!: State; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/entry/styles.less b/application/client/src/app/ui/tabs/observe/config-schema/entry/styles.less deleted file mode 100644 index 3919c9833..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/entry/styles.less +++ /dev/null @@ -1,6 +0,0 @@ -@import '../../../../styles/variables.less'; - -:host { - position: relative; - display: block; -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/entry/template.html b/application/client/src/app/ui/tabs/observe/config-schema/entry/template.html deleted file mode 100644 index e18cc543e..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/entry/template.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - diff --git a/application/client/src/app/ui/tabs/observe/config-schema/module.ts b/application/client/src/app/ui/tabs/observe/config-schema/module.ts deleted file mode 100644 index d1e9030a1..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/module.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatCardModule } from '@angular/material/card'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatButtonModule } from '@angular/material/button'; -import { MatSelectModule } from '@angular/material/select'; -import { MatChipsModule } from '@angular/material/chips'; -import { MatIconModule } from '@angular/material/icon'; -import { MatDividerModule } from '@angular/material/divider'; - -import { ConfigSchemas } from './component'; -import { ConfgiSchemaEntry } from './entry/component'; -import { ConfigSchemaBool } from './renders/bool/component'; -import { ConfigSchemaInteger } from './renders/integer/component'; -import { ConfigSchemaFloat } from './renders/float/component'; -import { ConfigSchemaString } from './renders/string/component'; -import { ConfigSchemaDropdown } from './renders/dropdown/component'; -import { ConfigSchemaFiles } from './renders/files/component'; -import { ConfigSchemaDirs } from './renders/dirs/component'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - MatCheckboxModule, - MatCardModule, - MatFormFieldModule, - MatInputModule, - MatButtonModule, - MatSelectModule, - MatChipsModule, - MatIconModule, - MatDividerModule, - ], - declarations: [ - ConfigSchemas, - ConfgiSchemaEntry, - ConfigSchemaBool, - ConfigSchemaInteger, - ConfigSchemaFloat, - ConfigSchemaString, - ConfigSchemaDropdown, - ConfigSchemaFiles, - ConfigSchemaDirs, - ], - exports: [ConfigSchemas], - bootstrap: [ - ConfigSchemas, - ConfgiSchemaEntry, - ConfigSchemaBool, - ConfigSchemaInteger, - ConfigSchemaFloat, - ConfigSchemaString, - ConfigSchemaDropdown, - ConfigSchemaFiles, - ConfigSchemaDirs, - ], -}) -export class ConfigSchmasModule {} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/bool/component.ts b/application/client/src/app/ui/tabs/observe/config-schema/renders/bool/component.ts deleted file mode 100644 index 029545a83..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/bool/component.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Component, ChangeDetectorRef, Input, SimpleChange, AfterContentInit } from '@angular/core'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { PluginConfigSchemaItem } from '@platform/types/bindings/plugins'; -import { State } from '../../state'; - -@Component({ - selector: 'app-tabs-config-schema-bool', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -export class ConfigSchemaBool extends ChangesDetector implements AfterContentInit { - @Input() public config!: PluginConfigSchemaItem; - @Input() public state!: State; - - public value?: boolean; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - ngAfterContentInit(): void { - const input_type = this.config.input_type; - this.value = this.state.isBooleanItem(input_type) ? input_type.Boolean : false; - this.state.saveConfig(this.config.id, { Boolean: this.value }); - } - - public ngOnCheckboxChange(event: SimpleChange): void { - const val = event as unknown as boolean; - this.state.saveConfig(this.config.id, { Boolean: val }); - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/bool/styles.less b/application/client/src/app/ui/tabs/observe/config-schema/renders/bool/styles.less deleted file mode 100644 index 3abbabe19..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/bool/styles.less +++ /dev/null @@ -1 +0,0 @@ -@import '../../../../../styles/variables.less'; diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/bool/template.html b/application/client/src/app/ui/tabs/observe/config-schema/renders/bool/template.html deleted file mode 100644 index 432b9b22a..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/bool/template.html +++ /dev/null @@ -1,9 +0,0 @@ - - {{config.title}} - -

{{config.description}}

diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/dirs/component.ts b/application/client/src/app/ui/tabs/observe/config-schema/renders/dirs/component.ts deleted file mode 100644 index c50d7e7ae..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/dirs/component.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Component, ChangeDetectorRef, Input, AfterContentInit } from '@angular/core'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { PluginConfigSchemaItem } from '@platform/types/bindings'; -import { State } from '../../state'; -import { bridge } from '@service/bridge'; - -@Component({ - selector: 'app-tabs-config-schema-dirs', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -export class ConfigSchemaDirs extends ChangesDetector implements AfterContentInit { - @Input() public config!: PluginConfigSchemaItem; - @Input() public state!: State; - - public paths: string[] = []; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngOnAddButtonkclick(): void { - bridge - .folders() - .select() - .then((dirs: string[]) => { - dirs = dirs.filter((added) => { - return this.paths.find((exist) => exist === added) === undefined; - }); - this.paths = this.paths.concat(dirs); - }) - .catch((err: Error) => { - console.error(`Error while opening folders: ${err.message}`); - }) - .finally(() => { - this.update(); - this.detectChanges(); - }); - } - - ngAfterContentInit(): void { - this.update(); - } - - public ngOnRemovePath(dir: string): void { - this.paths = this.paths.filter((f) => f !== dir); - this.update(); - this.detectChanges(); - } - - update(): void { - this.state.saveConfig(this.config.id, { Directories: this.paths }); - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/dirs/styles.less b/application/client/src/app/ui/tabs/observe/config-schema/renders/dirs/styles.less deleted file mode 100644 index 2d749f42f..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/dirs/styles.less +++ /dev/null @@ -1,27 +0,0 @@ -@import '../../../../../styles/variables.less'; -:host { - & mat-form-field { - display: block; - } - & div.controlls { - text-align: right; - } - & button { - position: relative; - display: inline-block; - margin: 6px 0 6px 6px; - } - & .chip-listbox { - display: flex; - flex-wrap: wrap; - gap: 8px; - } - & .chip-path { - max-width: 220px; /* Set a fixed width */ - overflow: hidden; /* Hide overflowing content */ - white-space: nowrap; /* Prevent text wrapping */ - direction: rtl; /* Align text to the right */ - text-overflow: ellipsis; /* Show ellipsis at the start */ - text-align: left; /* Ensure the visible part of the text is left-aligned */ - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/dirs/template.html b/application/client/src/app/ui/tabs/observe/config-schema/renders/dirs/template.html deleted file mode 100644 index 813e81934..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/dirs/template.html +++ /dev/null @@ -1,23 +0,0 @@ -

{{config.title}}

-
- - -

{{ path }}

- -
-
-
- -
- -
-

{{config.description}}

diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/dropdown/component.ts b/application/client/src/app/ui/tabs/observe/config-schema/renders/dropdown/component.ts deleted file mode 100644 index 82f444c34..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/dropdown/component.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - Component, - ChangeDetectorRef, - Input, - AfterContentInit, - SimpleChanges, -} from '@angular/core'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { PluginConfigSchemaItem } from '@platform/types/bindings'; -import { State } from '../../state'; - -@Component({ - selector: 'app-tabs-config-schema-dropdown', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -export class ConfigSchemaDropdown extends ChangesDetector implements AfterContentInit { - @Input() public config!: PluginConfigSchemaItem; - @Input() public state!: State; - - public selectedOption?: string; - public allOptions?: string[]; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - ngAfterContentInit(): void { - const input_type = this.config.input_type; - if (this.state.isDropdownItem(input_type)) { - this.allOptions = input_type.Dropdown[0]; - this.selectedOption = input_type.Dropdown[1]; - - this.state.saveConfig(this.config.id, { - Dropdown: this.selectedOption, - }); - } - } - - public ngOnSelectionChange(event: SimpleChanges): void { - const opt = event as unknown as string; - this.state.saveConfig(this.config.id, { - Dropdown: opt, - }); - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/dropdown/styles.less b/application/client/src/app/ui/tabs/observe/config-schema/renders/dropdown/styles.less deleted file mode 100644 index a77a7220b..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/dropdown/styles.less +++ /dev/null @@ -1,7 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - & mat-form-field { - display: block; - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/dropdown/template.html b/application/client/src/app/ui/tabs/observe/config-schema/renders/dropdown/template.html deleted file mode 100644 index 2f43bba2b..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/dropdown/template.html +++ /dev/null @@ -1,7 +0,0 @@ - - {{config.title}} - - {{option}} - - {{config.description}} - diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/files/component.ts b/application/client/src/app/ui/tabs/observe/config-schema/renders/files/component.ts deleted file mode 100644 index f1a5abb64..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/files/component.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Component, ChangeDetectorRef, Input, AfterContentInit } from '@angular/core'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { PluginConfigSchemaItem } from '@platform/types/bindings'; -import { State } from '../../state'; -import { File } from '@platform/types/files'; -import { bridge } from '@service/bridge'; - -@Component({ - selector: 'app-tabs-config-schema-files', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -export class ConfigSchemaFiles extends ChangesDetector implements AfterContentInit { - @Input() public config!: PluginConfigSchemaItem; - @Input() public state!: State; - - public paths: File[] = []; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngOnAddButtonkclick(): void { - const inputType = this.config.input_type; - const exts = - typeof inputType === 'object' && 'Files' in inputType && inputType.Files.length > 0 - ? inputType.Files.join(',') - : '*'; - bridge - .files() - .select.custom(exts) - .then((files: File[]) => { - files = files.filter((added) => { - return ( - this.paths.find((exist) => exist.filename === added.filename) === undefined - ); - }); - this.paths = this.paths.concat(files); - }) - - .catch((err: Error) => { - console.error(`Error while opening folders: ${err.message}`); - }) - .finally(() => { - this.update(); - this.detectChanges(); - }); - } - - ngAfterContentInit(): void { - this.update(); - } - - public ngOnRemovePath(file: File): void { - this.paths = this.paths.filter((f) => f.filename !== file.filename); - this.update(); - this.detectChanges(); - } - - update(): void { - const files = this.paths.map((p) => p.filename); - this.state.saveConfig(this.config.id, { Files: files }); - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/files/styles.less b/application/client/src/app/ui/tabs/observe/config-schema/renders/files/styles.less deleted file mode 100644 index 39952dc70..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/files/styles.less +++ /dev/null @@ -1,14 +0,0 @@ -@import '../../../../../styles/variables.less'; -:host { - & mat-form-field { - display: block; - } - & div.controlls { - text-align: right; - } - & button { - position: relative; - display: inline-block; - margin: 6px 0 6px 6px; - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/files/template.html b/application/client/src/app/ui/tabs/observe/config-schema/renders/files/template.html deleted file mode 100644 index 92dc2a7fb..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/files/template.html +++ /dev/null @@ -1,19 +0,0 @@ -

{{config.title}}

-
- - - {{path.name}} - - - -
- -
- -
-

{{config.description}}

diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/float/component.ts b/application/client/src/app/ui/tabs/observe/config-schema/renders/float/component.ts deleted file mode 100644 index 262c53b3a..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/float/component.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - Component, - ChangeDetectorRef, - Input, - AfterContentInit, - OnChanges, - SimpleChanges, -} from '@angular/core'; - -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { PluginConfigSchemaItem } from '@platform/types/bindings'; -import { State } from '../../state'; - -@Component({ - selector: 'app-tabs-config-schema-float', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -export class ConfigSchemaFloat extends ChangesDetector implements AfterContentInit, OnChanges { - @Input() public config!: PluginConfigSchemaItem; - @Input() public state!: State; - - public value?: number; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes['value']) { - this.state.saveConfig(this.config.id, { - Float: parseFloat(changes['value'].currentValue), - }); - } - } - - ngAfterContentInit(): void { - const input_type = this.config.input_type; - - this.value = this.state.isFloatItem(input_type) ? input_type.Float : 0.0; - this.state.saveConfig(this.config.id, { - Float: this.value, - }); - } - - ngOnInputChange(event: Event): void { - const target = event.target as HTMLInputElement; - if (target) { - this.state.saveConfig(this.config.id, { Float: parseFloat(target.value) }); - } - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/float/styles.less b/application/client/src/app/ui/tabs/observe/config-schema/renders/float/styles.less deleted file mode 100644 index 873d484f6..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/float/styles.less +++ /dev/null @@ -1,12 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - & mat-form-field { - display: block; - } - & input::-webkit-outer-spin-button, - & input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/float/template.html b/application/client/src/app/ui/tabs/observe/config-schema/renders/float/template.html deleted file mode 100644 index 6b510c4ac..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/float/template.html +++ /dev/null @@ -1,11 +0,0 @@ - - {{config.title}} - - {{config.description}} - diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/integer/component.ts b/application/client/src/app/ui/tabs/observe/config-schema/renders/integer/component.ts deleted file mode 100644 index 6b1180a1f..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/integer/component.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - Component, - ChangeDetectorRef, - Input, - AfterContentInit, - OnChanges, - SimpleChanges, -} from '@angular/core'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { PluginConfigSchemaItem } from '@platform/types/bindings'; -import { State } from '../../state'; - -@Component({ - selector: 'app-tabs-config-schema-integer', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -export class ConfigSchemaInteger extends ChangesDetector implements AfterContentInit, OnChanges { - @Input() public config!: PluginConfigSchemaItem; - @Input() public state!: State; - - public value?: number; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes['value']) { - this.state.saveConfig(this.config.id, { - Integer: parseInt(changes['value'].currentValue, 10), - }); - } - } - - ngAfterContentInit(): void { - const input_type = this.config.input_type; - this.value = this.state.isIntegerItem(input_type) ? input_type.Integer : 0; - this.state.saveConfig(this.config.id, { Integer: this.value }); - } - - public ngOnInputChange(event: Event): void { - const target = event.target as HTMLInputElement; - if (target) { - this.state.saveConfig(this.config.id, { Integer: parseInt(target.value, 10) }); - } - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/integer/styles.less b/application/client/src/app/ui/tabs/observe/config-schema/renders/integer/styles.less deleted file mode 100644 index 873d484f6..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/integer/styles.less +++ /dev/null @@ -1,12 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - & mat-form-field { - display: block; - } - & input::-webkit-outer-spin-button, - & input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/integer/template.html b/application/client/src/app/ui/tabs/observe/config-schema/renders/integer/template.html deleted file mode 100644 index 6b510c4ac..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/integer/template.html +++ /dev/null @@ -1,11 +0,0 @@ - - {{config.title}} - - {{config.description}} - diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/string/component.ts b/application/client/src/app/ui/tabs/observe/config-schema/renders/string/component.ts deleted file mode 100644 index 321e1a88d..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/string/component.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - Component, - ChangeDetectorRef, - Input, - AfterContentInit, - OnChanges, - SimpleChanges, -} from '@angular/core'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { PluginConfigSchemaItem } from '@platform/types/bindings/plugins'; -import { State } from '../../state'; - -@Component({ - selector: 'app-tabs-config-schema-string', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -export class ConfigSchemaString extends ChangesDetector implements AfterContentInit, OnChanges { - @Input() public config!: PluginConfigSchemaItem; - @Input() public state!: State; - - public value?: string; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes['value']) { - this.state.saveConfig(this.config.id, { - Text: changes['value'].currentValue, - }); - } - } - - ngAfterContentInit(): void { - const input_type = this.config.input_type; - this.value = this.state.isTextItem(input_type) ? input_type.Text : ''; - this.state.saveConfig(this.config.id, { Text: this.value }); - } - - public ngOnInputChange(event: Event): void { - const target = event.target as HTMLInputElement; - this.state.saveConfig(this.config.id, { Text: target?.value ?? '' }); - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/string/styles.less b/application/client/src/app/ui/tabs/observe/config-schema/renders/string/styles.less deleted file mode 100644 index a77a7220b..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/string/styles.less +++ /dev/null @@ -1,7 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - & mat-form-field { - display: block; - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/renders/string/template.html b/application/client/src/app/ui/tabs/observe/config-schema/renders/string/template.html deleted file mode 100644 index 01ce426b6..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/renders/string/template.html +++ /dev/null @@ -1,10 +0,0 @@ - - {{config.title}} - - {{config.description}} - diff --git a/application/client/src/app/ui/tabs/observe/config-schema/state.ts b/application/client/src/app/ui/tabs/observe/config-schema/state.ts deleted file mode 100644 index 7a1af2d44..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/state.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - PluginConfigSchemaItem, - PluginConfigSchemaType, - PluginConfigValue, -} from '@platform/types/bindings/plugins'; - -import { State as ParserState } from '../parsers/general/plugin/state'; - -export class State { - private _parserState?: ParserState; - - public schemas: PluginConfigSchemaItem[] = []; - - public reload(parent: ParserState) { - this.schemas = parent.selectedParser?.info.config_schemas ?? []; - this._parserState = parent; - } - - public saveConfig(id: string, value: PluginConfigValue) { - this._parserState?.saveConfig(id, value); - } - - public isBooleanItem(schema: PluginConfigSchemaType): schema is { Boolean: boolean } { - return typeof schema === 'object' && 'Boolean' in schema; - } - - public isIntegerItem(schema: PluginConfigSchemaType): schema is { Integer: number } { - return typeof schema === 'object' && 'Integer' in schema; - } - - public isFloatItem(schema: PluginConfigSchemaType): schema is { Float: number } { - return typeof schema === 'object' && 'Float' in schema; - } - - public isTextItem(schema: PluginConfigSchemaType): schema is { Text: string } { - return typeof schema === 'object' && 'Text' in schema; - } - - public isDirectoriesItem(schema: PluginConfigSchemaType): schema is 'Directories' { - return schema === 'Directories'; - } - - public isDropdownItem( - schema: PluginConfigSchemaType, - ): schema is { Dropdown: [Array, string] } { - return typeof schema === 'object' && 'Dropdown' in schema; - } - - public isFilesPicker(schema: PluginConfigSchemaType): schema is { Files: Array } { - return typeof schema === 'object' && 'Files' in schema; - } -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/styles.less b/application/client/src/app/ui/tabs/observe/config-schema/styles.less deleted file mode 100644 index b372fafb5..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/styles.less +++ /dev/null @@ -1,10 +0,0 @@ -@import '../../../styles/variables.less'; - -:host { - position: relative; - display: block; -} - -.itemspadding { - padding-bottom: 20px; -} diff --git a/application/client/src/app/ui/tabs/observe/config-schema/template.html b/application/client/src/app/ui/tabs/observe/config-schema/template.html deleted file mode 100644 index 8438e44a7..000000000 --- a/application/client/src/app/ui/tabs/observe/config-schema/template.html +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/application/client/src/app/ui/tabs/observe/error/component.ts b/application/client/src/app/ui/tabs/observe/error/component.ts deleted file mode 100644 index 529738611..000000000 --- a/application/client/src/app/ui/tabs/observe/error/component.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Component, ChangeDetectorRef, Input, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { Observe } from '@platform/types/observe'; - -@Component({ - selector: 'app-tabs-observe-error-state', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class TabObserveErrorState extends ChangesDetector implements AfterContentInit { - @Input() observe!: Observe; - - public error: string | undefined; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - this.env().subscriber.register( - this.observe.subscribe(() => { - const error = this.observe.validate(); - this.error = error instanceof Error ? error.message : undefined; - this.detectChanges(); - }), - ); - } -} -export interface TabObserveErrorState extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/error/module.ts b/application/client/src/app/ui/tabs/observe/error/module.ts deleted file mode 100644 index 55fc1bf05..000000000 --- a/application/client/src/app/ui/tabs/observe/error/module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { MatCardModule } from '@angular/material/card'; - -import { TabObserveErrorState } from './component'; - -@NgModule({ - imports: [CommonModule, MatCardModule], - declarations: [TabObserveErrorState], - exports: [TabObserveErrorState], - bootstrap: [TabObserveErrorState], -}) -export class ErrorStateModule {} diff --git a/application/client/src/app/ui/tabs/observe/error/styles.less b/application/client/src/app/ui/tabs/observe/error/styles.less deleted file mode 100644 index f3d8b743a..000000000 --- a/application/client/src/app/ui/tabs/observe/error/styles.less +++ /dev/null @@ -1,14 +0,0 @@ -@import '../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - top: 0; - left: 0; - width: 100%; - overflow: hidden; - & p.error { - color: var(--scheme-color-error-light); - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/error/template.html b/application/client/src/app/ui/tabs/observe/error/template.html deleted file mode 100644 index defc7f64f..000000000 --- a/application/client/src/app/ui/tabs/observe/error/template.html +++ /dev/null @@ -1,6 +0,0 @@ - - Error(s) - -

{{error}}

-
-
\ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/origin/concat/component.ts b/application/client/src/app/ui/tabs/observe/origin/concat/component.ts deleted file mode 100644 index 0b9946a46..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/concat/component.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - Component, - ChangeDetectorRef, - AfterViewInit, - Input, - AfterContentInit, -} from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { State } from '../../state'; -import { State as ParserState } from '@ui/tabs/observe/parsers/state'; - -import * as Origins from '@platform/types/observe/origin/index'; -import * as Parsers from '@platform/types/observe/parser/index'; -@Component({ - selector: 'app-tabs-observe-concat', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class TabObserveConcat extends ChangesDetector implements AfterViewInit, AfterContentInit { - @Input() state!: State; - - public parser!: ParserState; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - const origin = this.state.observe.origin.as( - Origins.Concat.Configuration, - ); - if (origin === undefined) { - throw new Error(`Current origin isn't a stream`); - } - this.parser = new ParserState(this.state.observe); - this.env().subscriber.register( - this.state.updates.get().parser.subscribe(() => { - const parser = this.state.getParser().embedded(); - if (parser === undefined) { - return; - } - this.state.observe.parser.overwrite({ - [parser]: Parsers.getByAlias(parser).configuration, - }); - }), - ); - } - - public ngAfterViewInit(): void { - // - } -} -export interface TabObserveConcat extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/concat/module.ts b/application/client/src/app/ui/tabs/observe/origin/concat/module.ts deleted file mode 100644 index c8521eed1..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/concat/module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { MatCardModule } from '@angular/material/card'; -import { ParserGeneralConfigurationModule } from '@ui/tabs/observe/parsers/general/module'; -import { ParserExtraConfigurationModule } from '@ui/tabs/observe/parsers/extra/module'; - -import { TabObserveConcat } from './component'; - -@NgModule({ - imports: [ - CommonModule, - MatCardModule, - ParserGeneralConfigurationModule, - ParserExtraConfigurationModule, - ], - declarations: [TabObserveConcat], - exports: [TabObserveConcat], - bootstrap: [TabObserveConcat], -}) -export class ConcatModule {} diff --git a/application/client/src/app/ui/tabs/observe/origin/concat/styles.less b/application/client/src/app/ui/tabs/observe/origin/concat/styles.less deleted file mode 100644 index 7066106ca..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/concat/styles.less +++ /dev/null @@ -1,37 +0,0 @@ -@import '../../../../styles/variables.less'; - - -:host { - position: absolute; - display: block; - top: 0; - left: 0; - width: 100%; - height: 100%; - overflow: hidden; - & div.title { - position: absolute; - display: flex; - flex-direction: row; - align-items: center; - left:0; - right: 0; - padding: 0 8px; - background: var(--scheme-color-5); - box-shadow: 0 3px 3px rgba(0,0,0,0.4); - height: 32px; - overflow: hidden; - & > * { - margin: 0 8px; - } - span.filler { - flex:auto; - width: 100%; - margin: 0; - } - p.info { - color: var(--scheme-color-1); - white-space: nowrap; - } - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/origin/concat/template.html b/application/client/src/app/ui/tabs/observe/origin/concat/template.html deleted file mode 100644 index 19eafb4b9..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/concat/template.html +++ /dev/null @@ -1,6 +0,0 @@ -
- -
-
- -
diff --git a/application/client/src/app/ui/tabs/observe/origin/file/component.ts b/application/client/src/app/ui/tabs/observe/origin/file/component.ts deleted file mode 100644 index 970234317..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/file/component.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - Component, - ChangeDetectorRef, - AfterViewInit, - Input, - AfterContentInit, -} from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { State } from '../../state'; -import { State as ParserState } from '@ui/tabs/observe/parsers/state'; - -import * as Origins from '@platform/types/observe/origin/index'; -import * as Parsers from '@platform/types/observe/parser/index'; -@Component({ - selector: 'app-tabs-observe-file', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class TabObserveFile extends ChangesDetector implements AfterViewInit, AfterContentInit { - @Input() state!: State; - - public parser!: ParserState; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - const origin = this.state.observe.origin.as( - Origins.File.Configuration, - ); - if (origin === undefined) { - throw new Error(`Current origin isn't a stream`); - } - this.parser = new ParserState(this.state.observe); - this.env().subscriber.register( - this.state.updates.get().parser.subscribe(() => { - const parser = this.state.getParser().embedded(); - if (parser === undefined) { - return; - } - this.state.observe.parser.overwrite({ - [parser]: Parsers.getByAlias(parser).configuration, - }); - }), - ); - } - - public ngAfterViewInit(): void { - // - } -} -export interface TabObserveFile extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/file/module.ts b/application/client/src/app/ui/tabs/observe/origin/file/module.ts deleted file mode 100644 index 12d44af10..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/file/module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { MatCardModule } from '@angular/material/card'; -import { ParserGeneralConfigurationModule } from '@ui/tabs/observe/parsers/general/module'; -import { ParserExtraConfigurationModule } from '@ui/tabs/observe/parsers/extra/module'; - -import { TabObserveFile } from './component'; - -@NgModule({ - imports: [ - CommonModule, - MatCardModule, - ParserGeneralConfigurationModule, - ParserExtraConfigurationModule, - ], - declarations: [TabObserveFile], - exports: [TabObserveFile], - bootstrap: [TabObserveFile], -}) -export class FileModule {} diff --git a/application/client/src/app/ui/tabs/observe/origin/file/styles.less b/application/client/src/app/ui/tabs/observe/origin/file/styles.less deleted file mode 100644 index a4b66d866..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/file/styles.less +++ /dev/null @@ -1,12 +0,0 @@ -@import '../../../../styles/variables.less'; - - -:host { - position: absolute; - display: block; - top: 0; - left: 0; - width: 100%; - height: 100%; - overflow: hidden; -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/origin/file/template.html b/application/client/src/app/ui/tabs/observe/origin/file/template.html deleted file mode 100644 index 19eafb4b9..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/file/template.html +++ /dev/null @@ -1,6 +0,0 @@ -
- -
-
- -
diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/component.ts deleted file mode 100644 index 40a8d6c5b..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/component.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Component, ChangeDetectorRef, Input, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { State } from '../../state'; -import { State as StreamState } from './transport/setup/state'; -import { State as ParserState } from '@ui/tabs/observe/parsers/state'; - -import * as Streams from '@platform/types/observe/origin/stream/index'; -import * as Origins from '@platform/types/observe/origin/index'; -import * as Parsers from '@platform/types/observe/parser/index'; - -@Component({ - selector: 'app-tabs-observe-stream', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class TabObserveStream extends ChangesDetector implements AfterContentInit { - @Input() state!: State; - - public stream!: StreamState; - public parser!: ParserState; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - const origin = this.state.observe.origin.as( - Origins.Stream.Configuration, - ); - if (origin === undefined) { - throw new Error(`Current origin isn't a stream`); - } - this.stream = new StreamState(this.state.action, origin.instance); - this.parser = new ParserState(this.state.observe); - this.env().subscriber.register( - this.state.updates.get().stream.subscribe(() => { - const stream = this.state.stream; - if (stream === undefined) { - return; - } - this.stream.from({ [stream]: Streams.getByAlias(stream).configuration }); - }), - this.state.updates.get().parser.subscribe(() => { - const parser = this.state.getParser().embedded(); - if (parser === undefined) { - return; - } - this.state.observe.parser.overwrite({ - [parser]: Parsers.getByAlias(parser).configuration, - }); - }), - ); - } -} -export interface TabObserveStream extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/module.ts b/application/client/src/app/ui/tabs/observe/origin/stream/module.ts deleted file mode 100644 index cf5838302..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/module.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { MatCardModule } from '@angular/material/card'; - -import { TabObserveStream } from './component'; -import { StreamsModule } from './transport/setup/module'; -import { ParserGeneralConfigurationModule } from '@ui/tabs/observe/parsers/general/module'; -import { RecentActionsModule } from '@elements/recent/module'; - -@NgModule({ - imports: [ - CommonModule, - MatCardModule, - StreamsModule, - ParserGeneralConfigurationModule, - RecentActionsModule, - ], - declarations: [TabObserveStream], - exports: [TabObserveStream], - bootstrap: [TabObserveStream], -}) -export class StreamModule {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/styles.less b/application/client/src/app/ui/tabs/observe/origin/stream/styles.less deleted file mode 100644 index a4b66d866..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/styles.less +++ /dev/null @@ -1,12 +0,0 @@ -@import '../../../../styles/variables.less'; - - -:host { - position: absolute; - display: block; - top: 0; - left: 0; - width: 100%; - height: 100%; - overflow: hidden; -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/template.html b/application/client/src/app/ui/tabs/observe/origin/stream/template.html deleted file mode 100644 index 12f9c2622..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/template.html +++ /dev/null @@ -1,17 +0,0 @@ -
- - Connection - - - - - - Recent - - - - -
-
- -
diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/listed/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/listed/component.ts deleted file mode 100644 index 676a432b2..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/listed/component.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { Component, ChangeDetectorRef, Input, AfterContentInit, HostListener } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { Session } from '@service/session/session'; -import { IMenuItem, contextmenu } from '@ui/service/contextmenu'; -import { ObserveOperation } from '@service/session/dependencies/observing/operation'; -import { stop } from '@ui/env/dom'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-transport-review', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class Transport extends ChangesDetector implements AfterContentInit { - @Input() public observe!: $.Observe; - @Input() public observer!: ObserveOperation | undefined; - @Input() public session!: Session; - @Input() public finished!: boolean; - - @HostListener('contextmenu', ['$event']) onContextMenu(event: MouseEvent) { - const items: IMenuItem[] = []; - const observer = this.observer; - if (this.observe.origin.files() !== undefined) { - if (this.observe.parser.instance instanceof $.Parser.Text.Configuration) { - // Text file can be opened just once per session - return; - } - } - const stream = this.observe.origin.as<$.Origin.Stream.Configuration>( - $.Origin.Stream.Configuration, - ); - if (observer !== undefined) { - items.push( - ...[ - { - caption: 'Stop', - handler: () => { - observer - .abort() - .catch((err: Error) => { - this.log().error( - `Fail to stop observe operation: ${err.message}`, - ); - }) - .finally(() => { - this.detectChanges(); - }); - }, - }, - { - caption: 'Restart', - handler: () => { - observer - .restart() - .catch((err: Error) => { - this.log().error( - `Fail to restart observe operation: ${err.message}`, - ); - }) - .finally(() => { - this.detectChanges(); - }); - }, - }, - ], - ); - } else if (observer === undefined) { - stream !== undefined && - items.push( - ...[ - { - caption: 'Restart', - handler: () => { - this.ilc() - .services.system.session.initialize() - .observe(this.observe, this.session) - .catch((err: Error) => { - this.log().error( - `Fail to restart observe operation: ${err.message}`, - ); - }) - .finally(() => { - this.detectChanges(); - }); - }, - }, - ], - ); - } - stream !== undefined && - items.push( - ...[ - {}, - { - caption: 'Parameters', - handler: () => { - this.ilc() - .services.system.session.initialize() - .configure(this.observe, this.session); - }, - }, - ], - ); - contextmenu.show({ - items: items, - x: event.pageX, - y: event.pageY, - }); - stop(event); - } - public description!: $.IOriginDetails | undefined; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - const description = this.observe.origin.desc(); - if (description instanceof Error) { - this.log().error(`Invalid description: ${description.message}`); - return; - } - this.description = description; - } -} -export interface Transport extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/listed/module.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/listed/module.ts deleted file mode 100644 index f5de015b8..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/listed/module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { MatIconModule } from '@angular/material/icon'; - -import { Transport } from './component'; - -@NgModule({ - imports: [CommonModule, MatIconModule], - declarations: [Transport], - exports: [Transport] -}) -export class TransportReviewModule {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/listed/styles.less b/application/client/src/app/ui/tabs/observe/origin/stream/transport/listed/styles.less deleted file mode 100644 index 29299fddc..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/listed/styles.less +++ /dev/null @@ -1,57 +0,0 @@ -@import '../../../../styles/variables.less'; - -:host { - position: relative; - display: flex; - padding: 12px 12px; - flex-direction: row; - & div.icon { - color: var(--scheme-color-2); - padding-right: 12px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - justify-items: center; - & mat-icon { - display: block; - font-size: 18px; - width: 18px; - height: 18px; - } - & span { - display: block; - } - &.running { - & mat-icon { - color: var(--scheme-color-0); - } - } - & span.type { - font-size: 11px; - } - & span.state { - font-size: 12px; - &.running { - color: var(--scheme-color-accent); - } - &.stopped { - color: var(--scheme-color-2); - } - } - } - & div.info { - position: relative; - overflow: hidden; - & p.major { - margin-bottom: 2px; - } - & p { - overflow: hidden; - text-overflow: ellipsis; - direction: rtl; - white-space: nowrap; - text-align: left; - } - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/listed/template.html b/application/client/src/app/ui/tabs/observe/origin/stream/transport/listed/template.html deleted file mode 100644 index 54d84787f..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/listed/template.html +++ /dev/null @@ -1,10 +0,0 @@ -
- {{description.icon}} - {{description.type}} - {{description.state.running}} - {{description.state.stopped}} -
-
-

{{description.major}}

-

{{description.minor}}

-
diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/process/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/process/component.ts deleted file mode 100644 index ef084659d..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/process/component.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { - Component, - ChangeDetectorRef, - AfterContentInit, - AfterViewInit, - Input, - ViewChild, - OnDestroy, -} from '@angular/core'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { FolderInput, Options as FoldersOptions } from '@elements/folderinput/component'; -import { - AutocompleteInput, - Options as AutocompleteOptions, -} from '@elements/autocomplete/component'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { State } from '../../states/process'; -import { components } from '@env/decorators/initial'; -import { Profile } from '@platform/types/bindings'; -import { Action } from '@ui/tabs/observe/action'; -import { Session } from '@service/session'; - -import * as Stream from '@platform/types/observe/origin/stream/index'; - -@Component({ - selector: 'app-process-setup-base', - template: '', - standalone: false, -}) -@Ilc() -export class SetupBase - extends ChangesDetector - implements AfterContentInit, AfterViewInit, OnDestroy -{ - @Input() public configuration!: Stream.Process.Configuration; - @Input() public action!: Action; - @Input() public session: Session | undefined; - - public state!: State; - - @ViewChild('cwd') public cwdInputRef!: FolderInput; - @ViewChild('cmd') public cmdInputRef!: AutocompleteInput; - - private _inputs!: { - cmd: AutocompleteOptions; - cwd: FoldersOptions; - }; - - protected setup(): void { - this.cmdInputRef !== undefined && - this.cmdInputRef.set(this.state.configuration.configuration.command); - this.cwdInputRef !== undefined && - this.cwdInputRef.set(this.state.configuration.configuration.cwd); - this.action.setDisabled(this.configuration.validate() instanceof Error); - if (this.state.configuration.configuration.cwd.trim() !== '') { - return; - } - this.ilc() - .services.system.bridge.cwd() - .get(undefined) - .then((cwd) => { - this.cwdInputRef.set(cwd); - this.configuration.configuration.cwd = cwd; - }) - .catch((err: Error) => { - this.log().error(`Fail to get cwd path: ${err.message}`); - }) - .finally(() => { - this.detectChanges(); - }); - } - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public setInputs(inputs: { cmd: AutocompleteOptions; cwd: FoldersOptions }): void { - this._inputs = inputs; - } - - public ngOnDestroy(): void { - this.state.destroy(); - } - - public ngAfterContentInit(): void { - this.state = new State(this.action, this.configuration); - this._inputs.cmd.defaults = this.state.configuration.configuration.command; - this._inputs.cwd.defaults = this.state.configuration.configuration.cwd; - this.ilc() - .services.system.bridge.env() - .get() - .then((envs) => { - this.state.configuration.configuration.envs = - Stream.Process.Configuration.sterilizeEnvVars(envs); - }) - .catch((err: Error) => { - this.log().error(`Fail to get envvars path: ${err.message}`); - }) - .finally(() => { - this.detectChanges(); - }); - this.ilc() - .services.system.bridge.os() - .envvars() - .then((envvars) => { - this.state.envvars = envvars; - }) - .catch((err: Error) => { - this.log().warn(`Fail to get no context envvars: ${err.message}`); - }) - .finally(() => { - this.detectChanges(); - }); - this.ilc() - .services.system.bridge.os() - .shells() - .then((profiles) => { - this.state - .setProfiles(profiles) - .catch((err: Error) => { - this.log().error(`Fail to set profiles: ${err.message}`); - }) - .finally(() => { - this.detectChanges(); - }); - }) - .catch((err: Error) => { - this.log().warn(`Fail to get a list of shell's profiles: ${err.message}`); - this.state - .setProfiles([]) - .catch((err: Error) => { - this.log().error(`Fail to set profiles: ${err.message}`); - }) - .finally(() => { - this.detectChanges(); - }); - }) - .finally(() => { - this.detectChanges(); - }); - this.env().subscriber.register( - this.configuration.subscribe(() => { - this.action.setDisabled(this.configuration.validate() instanceof Error); - this.detectChanges(); - }), - this.action.subjects.get().applied.subscribe(() => { - this._inputs.cmd.recent.emit(this.state.configuration.configuration.command); - this.state.configuration.configuration.cwd.trim() !== '' && - this.ilc() - .services.system.bridge.cwd() - .set(undefined, this.state.configuration.configuration.cwd) - .catch((err: Error) => { - this.log().error(`Fail to set cwd path: ${err.message}`); - }); - }), - ); - this.action.setDisabled(this.configuration.validate() instanceof Error); - } - - public ngAfterViewInit(): void { - this.setup(); - } - - public edit(target: 'cmd' | 'cwd', value: string): void { - if (target === 'cmd') { - this.state.configuration.configuration.command = value; - } else { - this.state.configuration.configuration.cwd = value; - } - } - - public enter(target: 'cmd' | 'cwd'): void { - if (this.cwdInputRef.error.is() || this.cmdInputRef.error.is()) { - return; - } - if (target === 'cmd' && this.configuration.validate() === undefined) { - this.action.apply(); - this.cmdInputRef.control.drop(); - } - this.markChangesForCheck(); - } - - public panel(): void { - this.markChangesForCheck(); - } - - public showEnvVars() { - this.ilc().services.ui.popup.open({ - component: { - factory: components.get('app-elements-pairs'), - inputs: { - map: this.state.getSelectedEnvs(), - }, - }, - closeOnKey: 'Escape', - uuid: 'app-elements-pairs', - }); - } - - public importEnvVars(profile: Profile | undefined) { - this.state.importEnvvarsFromShell(profile).catch((err: Error) => { - this.log().error(`Fail to save selected profile: ${err.message}`); - }); - this.detectChanges(); - } -} -export interface SetupBase extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/process/error.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/process/error.ts deleted file mode 100644 index b3c490f9c..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/process/error.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ErrorState } from '@elements/autocomplete/error'; -import { Subject } from '@platform/env/subscription'; - -export class CmdErrorState extends ErrorState { - protected updated: Subject = new Subject(); - protected error: string | undefined; - - public validate(): void { - const matches = this.value.match(/"/gi); - if (matches === null || matches.length === 0) { - this.error = undefined; - } else if (matches.length % 2 !== 0) { - this.error = `Not closed string: no closing "`; - } else { - this.error = undefined; - } - } - - public is(): boolean { - return this.error !== undefined; - } - - public msg(): string { - return this.error === undefined ? '' : this.error; - } - - public observer(): Subject { - return this.updated; - } -} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/serial/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/serial/component.ts deleted file mode 100644 index 237401751..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/serial/component.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { - Component, - ChangeDetectorRef, - Input, - OnDestroy, - AfterContentInit, - ViewChild, -} from '@angular/core'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { State } from '../../states/serial'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Action } from '@ui/tabs/observe/action'; -import { Session } from '@service/session'; -import { - AutocompleteInput, - Options as AutocompleteOptions, -} from '@elements/autocomplete/component'; -import { Subject } from '@platform/env/subscription'; -import { PathErrorState } from './error'; - -import * as Stream from '@platform/types/observe/origin/stream/index'; - -@Component({ - selector: 'app-serial-setup-base', - template: '', - standalone: false, -}) -@Ilc() -export class SetupBase extends ChangesDetector implements AfterContentInit, OnDestroy { - @Input() public configuration!: Stream.Serial.Configuration; - @Input() public action!: Action; - @Input() public session: Session | undefined; - @ViewChild('path') public pathInputRef!: AutocompleteInput; - - public state!: State; - public pathInputOptions: AutocompleteOptions = { - name: 'SerialPortPathRecentList', - storage: 'serialport_paths_recent', - defaults: '', - placeholder: 'Enter path to serial port', - label: 'Serial port path', - recent: new Subject(), - error: new PathErrorState(), - }; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - this.state = new State(this.action, this.configuration); - this.env().subscriber.register( - this.state.changed.subscribe(() => { - this.detectChanges(); - }), - ); - this.env().subscriber.register( - this.configuration.subscribe(() => { - this.action.setDisabled(this.configuration.validate() instanceof Error); - this.detectChanges(); - }), - this.action.subjects.get().applied.subscribe(() => { - this.action.setDisabled(this.configuration.validate() instanceof Error); - this.detectChanges(); - }), - ); - this.action.setDisabled(this.configuration.validate() instanceof Error); - this.state.scan().start(); - } - - public ngOnDestroy() { - this.state.scan().stop(); - this.state.destroy(); - } - - public selectDetectedPort(port: string): void { - this.state.configuration.configuration.path = port; - this.pathInputRef.set(port).focus(); - } - - public onPathChange(value: string): void { - this.state.configuration.configuration.path = value; - } - - public onPathEnter(): void { - if (this.pathInputRef.error.is() || this.state.configuration.validate() !== undefined) { - return; - } - this.action.apply(); - this.markChangesForCheck(); - } - - public panel(): void { - this.markChangesForCheck(); - } -} -export interface SetupBase extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/serial/error.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/serial/error.ts deleted file mode 100644 index 1921cae44..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/serial/error.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Subject } from '@platform/env/subscription'; -import { ErrorState } from '@elements/autocomplete/error'; - -export class PathErrorState extends ErrorState { - protected updated: Subject = new Subject(); - protected error: string | undefined; - - public validate(): void { - if (this.value.trim().length === 0) { - this.error = 'No path' - } else { - this.error = undefined; - } - } - - public is(): boolean { - return this.error !== undefined; - } - - public msg(): string { - return this.error === undefined ? '' : this.error; - } - - public observer(): Subject { - return this.updated; - } -} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/serial/module.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/serial/module.ts deleted file mode 100644 index 3a2c02aa6..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/serial/module.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatRadioModule } from '@angular/material/radio'; -import { MatCardModule } from '@angular/material/card'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatIconModule } from '@angular/material/icon'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; -import { MatInputModule } from '@angular/material/input'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - MatButtonModule, - MatCardModule, - MatDividerModule, - MatProgressBarModule, - MatIconModule, - MatFormFieldModule, - MatSelectModule, - MatInputModule, - MatAutocompleteModule, - MatRadioModule, - ], - declarations: [], - exports: [], -}) -export class BaseModule {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/tcp/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/tcp/component.ts deleted file mode 100644 index bb70e7bf0..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/tcp/component.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Component, ChangeDetectorRef, Input, OnDestroy, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { State } from '../../states/tcp'; -import { Action } from '@ui/tabs/observe/action'; -import { Session } from '@service/session'; - -import * as Stream from '@platform/types/observe/origin/stream/index'; - -@Component({ - selector: 'app-tcp-setup-base', - template: '', - standalone: false, -}) -@Ilc() -export class SetupBase extends ChangesDetector implements OnDestroy, AfterContentInit { - @Input() public configuration!: Stream.TCP.Configuration; - @Input() public action!: Action; - @Input() public session: Session | undefined; - - public state!: State; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngOnDestroy(): void { - this.state.destroy(); - } - - public ngAfterContentInit(): void { - this.state = new State(this.action, this.configuration); - this.env().subscriber.register( - this.configuration.subscribe(() => { - this.action.setDisabled(this.configuration.validate() instanceof Error); - this.detectChanges(); - }), - this.action.subjects.get().applied.subscribe(() => { - this.action.setDisabled(this.configuration.validate() instanceof Error); - this.detectChanges(); - }), - ); - this.action.setDisabled(this.configuration.validate() instanceof Error); - } -} -export interface SetupBase extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/tcp/error.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/tcp/error.ts deleted file mode 100644 index cdea46d46..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/tcp/error.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { ErrorStateMatcher } from '@angular/material/core'; -import { UntypedFormControl, FormGroupDirective, NgForm } from '@angular/forms'; - -import * as ip from '@platform/env/ipaddr'; - -export enum Field { - address = 'address', - multicastInterface = 'multicastInterface', -} - -export enum Codes { - NO_ERRORS = 'NO_ERRORS', - REQUIRED = 'REQUIRED', - INVALID = 'INVALID', -} - -export type UpdateHandler = () => void; - -export class ErrorState implements ErrorStateMatcher { - readonly _alias: Field; - readonly _updated: UpdateHandler; - private _code: Codes = Codes.NO_ERRORS; - - constructor(alias: Field, updated: UpdateHandler) { - this._alias = alias; - this._updated = updated; - } - - public isErrorState( - control: UntypedFormControl | null, - _form: FormGroupDirective | NgForm | null, - ): boolean { - if (control === null) { - return false; - } - if (this.isFieldRequired(control.value)) { - this._code = Codes.REQUIRED; - } else if (!this.isFieldValid(control.value)) { - this._code = Codes.INVALID; - } else { - this._code = Codes.NO_ERRORS; - } - this._updated(); - return this._code !== Codes.NO_ERRORS; - } - - public isFieldValid(value: string): boolean { - if (typeof value !== 'string') { - return false; - } - switch (this._alias) { - case Field.address: - return ( - ip.isValidIPv4(value) || - ip.isValidIPv6(value) || - ip.isValidIPv4WithPort(value) || - ip.isValidIPv6WithPort(value) - ); - case Field.multicastInterface: - return ( - ip.isValidIPv4(value) || - ip.isValidIPv6(value) || - ip.isValidIPv4WithPort(value) || - ip.isValidIPv6WithPort(value) - ); - default: - throw new Error(`Unexpected Field value: ${this._alias}`); - } - } - - public isFieldRequired(value: string): boolean { - if (typeof value !== 'string') { - return true; - } - switch (this._alias) { - case Field.address: - case Field.multicastInterface: - return value.trim() === ''; - } - } - - public getErrorCode(): Codes { - return this._code; - } - - public isValid(): boolean { - return this._code === Codes.NO_ERRORS; - } -} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/component.ts deleted file mode 100644 index 409586df1..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/component.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Component, ChangeDetectorRef, Input, OnDestroy, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { State } from '../../states/udp'; -import { Action } from '@ui/tabs/observe/action'; -import { Session } from '@service/session'; - -import * as Stream from '@platform/types/observe/origin/stream/index'; - -@Component({ - selector: 'app-udp-setup-base', - template: '', - standalone: false, -}) -@Ilc() -export class SetupBase extends ChangesDetector implements OnDestroy, AfterContentInit { - @Input() public configuration!: Stream.UDP.Configuration; - @Input() public action!: Action; - @Input() public session: Session | undefined; - - public state!: State; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngOnDestroy(): void { - this.state.destroy(); - } - - public ngAfterContentInit(): void { - this.state = new State(this.action, this.configuration); - this.env().subscriber.register( - this.configuration.subscribe(() => { - this.action.setDisabled(this.configuration.validate() instanceof Error); - this.detectChanges(); - }), - this.action.subjects.get().applied.subscribe(() => { - this.action.setDisabled(this.configuration.validate() instanceof Error); - this.detectChanges(); - }), - ); - this.action.setDisabled(this.configuration.validate() instanceof Error); - } - - public addMulticast() { - this.state.addMulticast(); - } - - public removeMulticast(index: number) { - this.state.removeMulticast(index); - } -} -export interface SetupBase extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/error.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/error.ts deleted file mode 100644 index fa36b206a..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/error.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { ErrorStateMatcher } from '@angular/material/core'; -import { UntypedFormControl, FormGroupDirective, NgForm } from '@angular/forms'; - -import * as ip from '@platform/env/ipaddr'; - -export enum Field { - bindingAddress = 'bindingAddress', - multicastAddress = 'multicastAddress', - multicastInterface = 'multicastInterface', -} - -export enum Codes { - NO_ERRORS = 'NO_ERRORS', - REQUIRED = 'REQUIRED', - INVALID = 'INVALID', -} - -export type UpdateHandler = () => void; - -export class ErrorState implements ErrorStateMatcher { - readonly _alias: Field; - readonly _updated: UpdateHandler; - private _code: Codes = Codes.NO_ERRORS; - - constructor(alias: Field, updated: UpdateHandler) { - this._alias = alias; - this._updated = updated; - } - - public isErrorState( - control: UntypedFormControl | null, - _form: FormGroupDirective | NgForm | null, - ): boolean { - if (control === null) { - return false; - } - const prev = this._code; - if (this.isFieldRequired(control.value)) { - this._code = Codes.REQUIRED; - } else if (!this.isFieldValid(control.value)) { - this._code = Codes.INVALID; - } else { - this._code = Codes.NO_ERRORS; - } - prev !== this._code && this._updated(); - return this._code !== Codes.NO_ERRORS; - } - - public isFieldValid(value: string): boolean { - if (typeof value !== 'string') { - return false; - } - switch (this._alias) { - case Field.bindingAddress: - case Field.multicastAddress: - return ( - ip.isValidIPv4(value) || - ip.isValidIPv6(value) || - ip.isValidIPv4WithPort(value) || - ip.isValidIPv6WithPort(value) - ); - case Field.multicastInterface: - return ip.isValidIPv4(value) || ip.isValidIPv6(value); - default: - throw new Error(`Unexpected Field value: ${this._alias}`); - } - } - - public isFieldRequired(value: string): boolean { - if (typeof value !== 'string') { - return true; - } - switch (this._alias) { - case Field.bindingAddress: - case Field.multicastAddress: - case Field.multicastInterface: - return value.trim() === ''; - } - } - - public getErrorCode(): Codes { - return this._code; - } - - public isValid(): boolean { - return this._code === Codes.NO_ERRORS; - } -} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/module.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/module.ts deleted file mode 100644 index d5d9f86ea..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatIconModule } from '@angular/material/icon'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; - -import { Multicast } from './multicast/component'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - MatButtonModule, - MatIconModule, - MatFormFieldModule, - MatInputModule, - ], - declarations: [Multicast], - exports: [Multicast] -}) -export class BaseModule {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/multicast/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/multicast/component.ts deleted file mode 100644 index 1cbf4793d..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/multicast/component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Component, Input, Output, EventEmitter } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import * as Errors from '../error'; - -import * as Stream from '@platform/types/observe/origin/stream/index'; - -@Component({ - selector: 'app-transport-udp-multicast', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class Multicast { - @Input() public multicast!: Stream.UDP.Multicast; - - public errors: { - multiaddr: Errors.ErrorState; - interface: Errors.ErrorState; - } = { - multiaddr: new Errors.ErrorState(Errors.Field.multicastAddress, () => { - // this.update(); - }), - interface: new Errors.ErrorState(Errors.Field.multicastAddress, () => { - // this.update(); - }), - }; - @Output() public clean: EventEmitter = new EventEmitter(); - - public onChanges() { - if ( - this.multicast.multiaddr.trim() !== '' || - (this.multicast.interface !== undefined && this.multicast.interface.trim() !== '') - ) { - return; - } - this.clean.next(); - } - - public onRemove() { - this.clean.next(); - } -} -export interface Multicast extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/multicast/styles.less b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/multicast/styles.less deleted file mode 100644 index 9b5a4a41c..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/multicast/styles.less +++ /dev/null @@ -1,26 +0,0 @@ -@import '../../../../../../../../../styles/variables.less'; - - -:host { - align-items: baseline; - position: relative; - display: flex; - margin: 0; - cursor: default; - padding-right: 6px; - & mat-form-field.inline { - margin-right: 12px; - } - & .mat-icon { - width: 16px; - height: 16px; - font-size: 16px; - color: var(--scheme-color-1); - &:hover { - color: var(--scheme-color-3); - } - } - & span.filler { - flex: auto; - } -} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/multicast/template.html b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/multicast/template.html deleted file mode 100644 index e1167c514..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/bases/udp/multicast/template.html +++ /dev/null @@ -1,22 +0,0 @@ - - Address - - - Please enter a valid IPv4/IPv6 address - - - required - - - - Interface Address - - - Please define valid interface - - - required - - - -close diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/process/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/process/component.ts deleted file mode 100644 index b4f320115..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/process/component.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - Component, - ChangeDetectorRef, - Input, - AfterContentInit, - AfterViewInit, - OnDestroy, - ViewEncapsulation, -} from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Options as AutocompleteOptions } from '@elements/autocomplete/component'; -import { Options as FoldersOptions } from '@elements/folderinput/component'; -import { Subject } from '@platform/env/subscription'; -import { CmdErrorState } from '../../bases/process/error'; -import { SetupBase } from '../../bases/process/component'; -import { Profile } from '@platform/types/bindings'; - -@Component({ - selector: 'app-transport-process', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - encapsulation: ViewEncapsulation.None, - standalone: false, -}) -@Ilc() -export class Setup extends SetupBase implements AfterContentInit, AfterViewInit, OnDestroy { - @Input() public update?: Subject; - - public readonly inputs: { - cmd: AutocompleteOptions; - cwd: FoldersOptions; - } = { - cmd: { - name: 'CommandsRecentList', - storage: 'processes_cmd_recent', - defaults: '', - placeholder: 'Enter terminal command', - label: 'Terminal command', - recent: new Subject(), - error: new CmdErrorState(), - }, - cwd: { - placeholder: 'Enter working folder', - label: 'Working folder', - defaults: '', - passive: true, - }, - }; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - this.setInputs(this.inputs); - } - - public override ngAfterContentInit(): void { - this.update !== undefined && - this.env().subscriber.register( - this.update.subscribe(() => { - this.cmdInputRef.set(this.state.configuration.configuration.command); - this.cwdInputRef.set(this.state.configuration.configuration.cwd); - }), - ); - super.ngAfterContentInit(); - } - - public getEnvvarsCount(profile: Profile) { - return profile.envvars ? profile.envvars.size : 0; - } -} -export interface Setup extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/process/module.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/process/module.ts deleted file mode 100644 index 351a77679..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/process/module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { AutocompleteModule } from '@elements/autocomplete/module'; -import { FolderInputModule } from '@elements/folderinput/module'; -import { MatMenuModule } from '@angular/material/menu'; -import { MatDividerModule } from '@angular/material/divider'; -import { Setup } from './component'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; - -@NgModule({ - imports: [ - CommonModule, - AutocompleteModule, - FolderInputModule, - MatMenuModule, - MatDividerModule, - MatProgressBarModule, - MatProgressSpinnerModule, - ], - declarations: [Setup], - exports: [Setup] -}) -export class SetupModule {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/process/styles.less b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/process/styles.less deleted file mode 100644 index 992a05aa7..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/process/styles.less +++ /dev/null @@ -1,61 +0,0 @@ -@import '../../../../../../../../styles/variables.less'; - - -app-transport-process { - position: relative; - display: block; - width: 100%; - & div.command { - position: relative; - display: flex; - width: 100%; - align-items: center; - margin-bottom: 12px; - & button { - margin-left: 6px; - } - } -} -.app-transport-process-menu { - & button[mat-menu-item] { - & div.shell-profile { - position: relative; - margin-top: -7px; - & > span { - height: 16px; - line-height: 16px; - } - & > span.envvars-count { - color: var(--scheme-color-3); - } - & > span.shell-path { - position: relative; - display: block; - color: var(--scheme-color-3); - font-size: 10px; - max-width: 250px; - overflow: hidden; - text-overflow: ellipsis; - margin-top: -8px; - white-space: nowrap; - direction: rtl; - max-width: 200px; - } - } - &[data-selected="true"] { - & div.shell-profile { - &::before { - display: block; - position: absolute; - width: 2px; - height: 24px; - background: var(--scheme-color-accent); - left: -9px; - top: 7px; - content: ''; - } - } - } - } - -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/process/template.html b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/process/template.html deleted file mode 100644 index 1c2757cde..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/process/template.html +++ /dev/null @@ -1,53 +0,0 @@ -
- - -
- - - - - -

Import variables from:

- -
- -

Loading profiles...

- -
- - -
diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/serial/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/serial/component.ts deleted file mode 100644 index de3561dee..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/serial/component.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Component, ChangeDetectorRef, Input, OnDestroy, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Subject } from '@platform/env/subscription'; -import { SetupBase } from '../../bases/serial/component'; - -@Component({ - selector: 'app-transport-serial', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class Setup extends SetupBase implements AfterContentInit, OnDestroy { - @Input() public update?: Subject; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public override ngAfterContentInit() { - this.update !== undefined && - this.env().subscriber.register( - this.update.subscribe(() => { - this.detectChanges(); - }), - ); - super.ngAfterContentInit(); - } -} -export interface TransportSerial extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/serial/module.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/serial/module.ts deleted file mode 100644 index 3856d61cf..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/serial/module.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatRadioModule } from '@angular/material/radio'; -import { MatCardModule } from '@angular/material/card'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatIconModule } from '@angular/material/icon'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; -import { MatInputModule } from '@angular/material/input'; -import { BaseModule } from '../../bases/serial/module'; -import { Setup } from './component'; -import { AutocompleteModule } from '@elements/autocomplete/module'; -import { MatMenuModule } from '@angular/material/menu'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - MatButtonModule, - MatCardModule, - MatDividerModule, - MatProgressBarModule, - MatIconModule, - MatFormFieldModule, - MatSelectModule, - MatInputModule, - AutocompleteModule, - MatRadioModule, - BaseModule, - MatProgressSpinnerModule, - MatMenuModule, - ], - declarations: [Setup], - exports: [Setup], -}) -export class SetupModule {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/serial/styles.less b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/serial/styles.less deleted file mode 100644 index bb4e6031d..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/serial/styles.less +++ /dev/null @@ -1,24 +0,0 @@ -@import '../../../../../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - p.settings { - margin: 8px 0; - } - & div.path { - position: relative; - display: flex; - width: 100%; - align-items: center; - margin-bottom: 12px; - & button { - margin-left: 6px; - } - } - div.controlls{ - text-align: right; - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/serial/template.html b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/serial/template.html deleted file mode 100644 index f97479eb5..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/serial/template.html +++ /dev/null @@ -1,96 +0,0 @@ -
- - -
- -

Settings

- - Baud Rate - - - {{value}} - - - - - Custom Baud Rate - - - - Data Bits - - - {{value}} - - - - - Flow Control - - - {{keyvalue.name}} - - - - - Parity - - - {{keyvalue.name}} - - - - - Stop Bits - - - {{value}} - - - - - Exclusive opening - - - {{keyvalue.name}} - - - - - Delay on writing - - - {{keyvalue.name}} - - - -
- -
- - - -

No ports found

-
- - - -
- -

Scanning for available ports...

- -
-
\ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/tcp/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/tcp/component.ts deleted file mode 100644 index ca5a8abf4..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/tcp/component.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Component, ChangeDetectorRef, Input, OnDestroy, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Subject } from '@platform/env/subscription'; -import { SetupBase } from '../../bases/tcp/component'; - -@Component({ - selector: 'app-transport-tcp', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class Setup extends SetupBase implements OnDestroy, AfterContentInit { - @Input() public update?: Subject; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public override ngAfterContentInit(): void { - this.update !== undefined && - this.env().subscriber.register( - this.update.subscribe(() => { - this.detectChanges(); - }), - ); - super.ngAfterContentInit(); - } -} -export interface Setup extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/tcp/styles.less b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/tcp/styles.less deleted file mode 100644 index 189000425..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/tcp/styles.less +++ /dev/null @@ -1,14 +0,0 @@ -@import '../../../../../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - & mat-form-field.full-width { - display: block; - } - & mat-form-field.inline { - margin-right: 12px; - } -} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/tcp/template.html b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/tcp/template.html deleted file mode 100644 index 8d0df136b..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/tcp/template.html +++ /dev/null @@ -1,20 +0,0 @@ - - IP Address - - - Please enter a valid IPv4/IPv6 address - - - required - - - diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/udp/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/udp/component.ts deleted file mode 100644 index 772ff593d..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/udp/component.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Component, ChangeDetectorRef, Input, OnDestroy, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Subject } from '@platform/env/subscription'; -import { SetupBase } from '../../bases/udp/component'; - -@Component({ - selector: 'app-transport-udp', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class Setup extends SetupBase implements OnDestroy, AfterContentInit { - @Input() public update?: Subject; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public override ngAfterContentInit(): void { - this.update !== undefined && - this.env().subscriber.register( - this.update.subscribe(() => { - this.detectChanges(); - }), - ); - super.ngAfterContentInit(); - } -} -export interface Setup extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/udp/module.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/udp/module.ts deleted file mode 100644 index d3cee77cb..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/udp/module.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatCardModule } from '@angular/material/card'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatIconModule } from '@angular/material/icon'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; -import { MatInputModule } from '@angular/material/input'; - -import { Setup } from './component'; -import { BaseModule } from '../../bases/udp/module'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - MatButtonModule, - MatCardModule, - MatDividerModule, - MatProgressBarModule, - MatIconModule, - MatFormFieldModule, - MatSelectModule, - MatInputModule, - BaseModule, - ], - declarations: [Setup], - exports: [Setup] -}) -export class SetupModule {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/udp/styles.less b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/udp/styles.less deleted file mode 100644 index 201ebcbb9..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/udp/styles.less +++ /dev/null @@ -1,28 +0,0 @@ -@import '../../../../../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - & mat-form-field.full-width { - display: block; - } - & mat-form-field.inline { - margin-right: 12px; - } - div.mutlicasts{ - margin: 12px 0; - & p { - padding-top: 6px; - } - } - div.controlls{ - text-align: right; - } - & button { - position: relative; - display: inline-block; - margin: 6px 0 6px 6px; - } -} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/udp/template.html b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/udp/template.html deleted file mode 100644 index 79aee38d2..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/udp/template.html +++ /dev/null @@ -1,20 +0,0 @@ - - IP Address - - - Please enter a valid IPv4/IPv6 address - - - required - - -
- -

Multicasts

-
- -
- -
diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/component.ts deleted file mode 100644 index 5f24f7655..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/component.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Component, ChangeDetectorRef, Input, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { State } from './state'; - -import * as Stream from '@platform/types/observe/origin/stream'; - -@Component({ - selector: 'app-transport', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class Transport extends ChangesDetector implements AfterContentInit { - public Source = Stream.Stream.Source; - - @Input() public state!: State; - - public instance: { - [Stream.Stream.Source.Process]: Stream.Stream.Process.Configuration | undefined; - [Stream.Stream.Source.Serial]: Stream.Stream.Serial.Configuration | undefined; - [Stream.Stream.Source.UDP]: Stream.Stream.UDP.Configuration | undefined; - [Stream.Stream.Source.TCP]: Stream.Stream.TCP.Configuration | undefined; - } = { - [Stream.Stream.Source.Process]: undefined, - [Stream.Stream.Source.Serial]: undefined, - [Stream.Stream.Source.UDP]: undefined, - [Stream.Stream.Source.TCP]: undefined, - }; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - this.env().subscriber.register( - this.state.updated.subscribe(() => { - this.update().detectChanges(); - }), - ); - this.update(); - } - - protected update(): Transport { - (this.instance as any) = { - [this.state.configuration.instance.alias()]: this.state.configuration.instance, - }; - return this; - } -} -export interface Transport extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/module.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/module.ts deleted file mode 100644 index 88c5de6b9..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/module.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; -import { SetupModule as UdpSetupModule } from './complete/udp/module'; -import { SetupModule as TcpSetupModule } from './complete/tcp/module'; -import { SetupModule as SerialSetupModule } from './complete/serial/module'; -import { SetupModule as ProcessSetupModule } from './complete/process/module'; - -import { Transport } from './component'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - MatButtonModule, - MatDividerModule, - MatFormFieldModule, - MatSelectModule, - UdpSetupModule, - TcpSetupModule, - SerialSetupModule, - ProcessSetupModule, - ], - declarations: [Transport], - exports: [Transport, UdpSetupModule, TcpSetupModule, SerialSetupModule, ProcessSetupModule], -}) -export class StreamsModule {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/process/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/process/component.ts deleted file mode 100644 index e0ed2fbcc..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/process/component.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - Component, - ChangeDetectorRef, - AfterContentInit, - OnDestroy, - ViewEncapsulation, -} from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Options as AutocompleteOptions } from '@elements/autocomplete/component'; -import { Options as FoldersOptions } from '@elements/folderinput/component'; -import { Subject } from '@platform/env/subscription'; -import { CmdErrorState } from '../../bases/process/error'; -import { SetupBase } from '../../bases/process/component'; -import { Profile } from '@platform/types/bindings'; - -@Component({ - selector: 'app-process-quicksetup', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - encapsulation: ViewEncapsulation.None, - standalone: false, -}) -@Ilc() -export class QuickSetup extends SetupBase implements AfterContentInit, OnDestroy { - public readonly inputs: { - cmd: AutocompleteOptions; - cwd: FoldersOptions; - } = { - cmd: { - name: 'CommandsRecentList', - storage: 'processes_cmd_recent', - defaults: '', - placeholder: 'Enter terminal command', - label: 'Terminal command', - recent: new Subject(), - error: new CmdErrorState(), - }, - cwd: { - placeholder: 'Enter working folder', - label: undefined, - defaults: '', - passive: true, - }, - }; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - this.setInputs(this.inputs); - } - - public getEnvvarsCount(profile: Profile) { - return profile.envvars ? profile.envvars.size : 0; - } - - public destroy(): Promise { - return Promise.resolve(); - } -} -export interface QuickSetup extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/process/module.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/process/module.ts deleted file mode 100644 index 684c7c771..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/process/module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { AutocompleteModule } from '@elements/autocomplete/module'; -import { FolderInputModule } from '@elements/folderinput/module'; -import { MatMenuModule } from '@angular/material/menu'; -import { MatDividerModule } from '@angular/material/divider'; -import { QuickSetup } from './component'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; - -@NgModule({ - imports: [ - CommonModule, - AutocompleteModule, - FolderInputModule, - MatMenuModule, - MatDividerModule, - MatProgressBarModule, - MatProgressSpinnerModule, - ], - declarations: [QuickSetup], - exports: [QuickSetup] -}) -export class QuickSetupModule {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/process/styles.less b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/process/styles.less deleted file mode 100644 index eb5b47318..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/process/styles.less +++ /dev/null @@ -1,67 +0,0 @@ -@import '../../../../../../../../styles/variables.less'; - - -app-process-quicksetup { - position: relative; - display: block; - width: 100%; - & div.command { - position: relative; - display: flex; - width: 100%; - align-items: center; - margin-bottom: 6px; - & button { - margin-left: 6px; - } - } - & mat-form-field.full-width { - display: block; - } - & mat-form-field.outline { - margin: 0!important; - } - -} -.app-transport-process-menu { - & .mat-menu-item { - & > div.shell-profile { - position: relative; - margin-top: -7px; - & > span { - height: 16px; - line-height: 16px; - } - & > span.envvars-count { - color: var(--scheme-color-3); - } - & > span.shell-path { - position: relative; - display: block; - color: var(--scheme-color-3); - font-size: 10px; - max-width: 250px; - overflow: hidden; - text-overflow: ellipsis; - margin-top: -8px; - white-space: nowrap; - direction: rtl; - max-width: 200px; - } - } - } - & .mat-menu-item[data-selected="true"] { - & > div.shell-profile { - &::before { - display: block; - position: absolute; - width: 2px; - height: 24px; - background: var(--scheme-color-accent); - left: -9px; - top: 11px; - content: ''; - } - } - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/process/template.html b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/process/template.html deleted file mode 100644 index 1c2757cde..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/process/template.html +++ /dev/null @@ -1,53 +0,0 @@ -
- - -
- - - - - -

Import variables from:

- -
- -

Loading profiles...

- -
- - -
diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/serial/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/serial/component.ts deleted file mode 100644 index 3f0bda043..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/serial/component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, ChangeDetectorRef, OnDestroy, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { SetupBase } from '../../bases/serial/component'; - -@Component({ - selector: 'app-serial-quicksetup', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class QuickSetup extends SetupBase implements AfterContentInit, OnDestroy { - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public connect() { - this.action.apply(); - } -} -export interface QuickSetup extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/serial/module.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/serial/module.ts deleted file mode 100644 index 95da2b450..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/serial/module.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatRadioModule } from '@angular/material/radio'; -import { MatCardModule } from '@angular/material/card'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatIconModule } from '@angular/material/icon'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; -import { MatInputModule } from '@angular/material/input'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; -import { BaseModule } from '../../bases/serial/module'; -import { QuickSetup } from './component'; -import { SetupModule } from '../../complete/serial/module'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - MatButtonModule, - MatCardModule, - MatDividerModule, - MatProgressBarModule, - MatIconModule, - MatFormFieldModule, - MatSelectModule, - MatInputModule, - MatAutocompleteModule, - MatRadioModule, - BaseModule, - SetupModule, - ], - declarations: [QuickSetup], - exports: [QuickSetup], -}) -export class QuickSetupModule {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/serial/styles.less b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/serial/styles.less deleted file mode 100644 index 32de7112f..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/serial/styles.less +++ /dev/null @@ -1,12 +0,0 @@ -@import '../../../../../../../../styles/variables.less'; - -:host { - position: relative; - display: block; - width: auto; - padding: 0 24px; - div.controlls{ - text-align: right; - padding: 8px 0 0 0; - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/serial/template.html b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/serial/template.html deleted file mode 100644 index 5ed1034fc..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/serial/template.html +++ /dev/null @@ -1,4 +0,0 @@ - -
- -
\ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/tcp/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/tcp/component.ts deleted file mode 100644 index 76101e954..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/tcp/component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, ChangeDetectorRef, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { SetupBase } from '../../bases/tcp/component'; - -@Component({ - selector: 'app-tcp-quicksetup', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class QuickSetup extends SetupBase implements AfterContentInit { - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public connect() { - this.action.apply(); - } -} -export interface QuickSetup extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/tcp/styles.less b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/tcp/styles.less deleted file mode 100644 index 537eda256..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/tcp/styles.less +++ /dev/null @@ -1,25 +0,0 @@ -@import '../../../../../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - & mat-form-field { - width: 100%; - } - & mat-form-field.full-width { - display: block; - } - & mat-form-field.inline { - margin-right: 12px; - } - div.controlls{ - text-align: right; - } - & button { - position: relative; - display: inline-block; - margin: 6px 0 6px 6px; - } -} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/tcp/template.html b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/tcp/template.html deleted file mode 100644 index 1ec165377..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/tcp/template.html +++ /dev/null @@ -1,13 +0,0 @@ - - IP Address - - - Please enter a valid IPv4/IPv6 address - - - required - - -
- -
\ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/udp/component.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/udp/component.ts deleted file mode 100644 index 8856acba5..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/udp/component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, ChangeDetectorRef, OnDestroy, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { SetupBase } from '../../bases/udp/component'; - -@Component({ - selector: 'app-udp-quicksetup', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class QuickSetup extends SetupBase implements OnDestroy, AfterContentInit { - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public connect() { - this.action.apply(); - } -} -export interface QuickSetup extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/udp/module.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/udp/module.ts deleted file mode 100644 index 5bf02f945..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/udp/module.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatCardModule } from '@angular/material/card'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatIconModule } from '@angular/material/icon'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; -import { MatInputModule } from '@angular/material/input'; - -import { QuickSetup } from './component'; -import { BaseModule } from '../../bases/udp/module'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - MatButtonModule, - MatCardModule, - MatDividerModule, - MatProgressBarModule, - MatIconModule, - MatFormFieldModule, - MatSelectModule, - MatInputModule, - BaseModule, - ], - declarations: [QuickSetup], - exports: [QuickSetup] -}) -export class QuickSetupModule {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/udp/styles.less b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/udp/styles.less deleted file mode 100644 index c7c410479..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/udp/styles.less +++ /dev/null @@ -1,31 +0,0 @@ -@import '../../../../../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - & mat-form-field { - width: 100%; - } - & mat-form-field.full-width { - display: block; - } - & mat-form-field.inline { - margin-right: 12px; - } - div.mutlicasts{ - margin: 12px 0; - & p { - padding-top: 6px; - } - } - div.controlls{ - text-align: right; - } - & button { - position: relative; - display: inline-block; - margin: 6px 0 6px 6px; - } -} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/udp/template.html b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/udp/template.html deleted file mode 100644 index 8fdbe9485..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/quick/udp/template.html +++ /dev/null @@ -1,21 +0,0 @@ - - IP Address - - - Please enter a valid IPv4/IPv6 address - - - required - - -
- -

Multicasts

-
- -
- - -
diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/state.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/state.ts deleted file mode 100644 index dfd8dd4c9..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/state.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Subject } from '@platform/env/subscription'; -import { Action } from '@ui/tabs/observe/action'; - -import * as Stream from '@platform/types/observe/origin/stream/index'; - -export class State { - protected readonly history: Map = new Map(); - - public source: Stream.Source; - public updated: Subject = new Subject(); - - constructor( - public readonly action: Action, - public readonly configuration: Stream.Configuration, - ) { - this.source = Stream.getAliasByConfiguration(configuration.configuration); - this.action = action; - } - - public destroy() { - this.updated.destroy(); - } - - public from(configuration: Stream.IConfiguration) { - this.history.set( - this.configuration.instance.alias(), - this.configuration.instance.configuration, - ); - this.configuration.change().byConfiguration(configuration); - this.source = Stream.getAliasByConfiguration(configuration); - this.updated.emit(); - } - - public switch(source: Stream.Source) { - this.history.set( - this.configuration.instance.alias(), - this.configuration.instance.configuration, - ); - this.configuration - .change() - .byDeclaration(Stream.getByAlias(source, this.history.get(source))); - this.source = source; - this.updated.emit(); - } -} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/states/process.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/states/process.ts deleted file mode 100644 index c7380b1d9..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/states/process.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Profile } from '@platform/types/bindings'; -import { bridge } from '@service/bridge'; -import { Destroy } from '@platform/types/env/types'; -import { Action } from '../../../../../action'; - -import * as obj from '@platform/env/obj'; -import * as Stream from '@platform/types/observe/origin/stream/index'; - -const ROOTS_STORAGE_KEY = 'user_selected_profile'; -const ENTRY_KEY = 'selected_profile_path'; - -export class State implements Destroy { - public profiles: { - all: Profile[] | undefined; - valid: Profile[] | undefined; - } = { - all: undefined, - valid: undefined, - }; - // No context envvars - public envvars: Map = new Map(); - public current: Profile | undefined; - - constructor( - public readonly action: Action, - public readonly configuration: Stream.Process.Configuration, - ) {} - - public destroy(): void { - // Having method "destroy()" is requirement of session's storage - } - - public setProfiles(profiles: Profile[]): Promise { - const valid: Profile[] = []; - profiles.forEach((profile) => { - valid.find((p) => p.path === profile.path) === undefined && - profile.envvars !== undefined && - valid.push(profile); - }); - this.profiles.all = profiles; - this.profiles.valid = valid; - return this.storage() - .get() - .then((path: string | undefined) => { - this.current = this.profiles.all?.find((p) => p.path === path); - if (this.current !== undefined && this.current.envvars !== undefined) { - this.configuration.configuration.envs = obj.mapToObj(this.current.envvars); - } - }); - } - - public isProfilesLoaded(): boolean { - return this.profiles.all !== undefined; - } - - public importEnvvarsFromShell(profile: Profile | undefined): Promise { - if (profile === undefined) { - this.current = undefined; - this.configuration.configuration.envs = obj.mapToObj(this.envvars); - return this.storage().set(undefined); - } else { - if (profile.envvars === undefined) { - return Promise.resolve(); - } - this.configuration.configuration.envs = obj.mapToObj(profile.envvars); - this.current = profile; - return this.storage().set(profile.path); - } - } - - public getSelectedEnvs(): Map { - return obj.objToStringMap(this.configuration.configuration.envs); - } - - public isShellSelected(profile: Profile): boolean { - return this.current ? profile.path === this.current.path : false; - } - - protected storage(): { - get(): Promise; - set(path: string | undefined): Promise; - } { - return { - get: (): Promise => { - return new Promise((resolve, reject) => { - bridge - .entries({ key: ROOTS_STORAGE_KEY }) - .get() - .then((entries) => { - resolve(entries.length === 0 ? undefined : entries[0].content); - }) - .catch(reject); - }); - }, - set: (path: string | undefined): Promise => { - if (path === undefined) { - return bridge.entries({ key: ROOTS_STORAGE_KEY }).delete([ENTRY_KEY]); - } else { - return bridge.entries({ key: ROOTS_STORAGE_KEY }).overwrite([ - { - uuid: ENTRY_KEY, - content: path, - }, - ]); - } - }, - }; - } -} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/states/serial.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/states/serial.ts deleted file mode 100644 index d902a5254..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/states/serial.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { bridge } from '@service/bridge'; -import { scope } from '@platform/env/scope'; -import { Subject } from '@platform/env/subscription'; -import { error } from '@platform/log/utils'; -import { Destroy } from '@platform/types/env/types'; -import { Action } from '../../../../../action'; - -import * as Stream from '@platform/types/observe/origin/stream/index'; - -const SERIAL_PORT_SETTINGS_STORAGE = 'serial_port_settings'; -const REGULAR_RESCAN_PORTS_DURATION_MS = 3000; -const NOPORTS_RESCAN_PORTS_DURATION_MS = 1000; - -const CUSTOM_BAUD_RATE_REF = 'Custom'; -const BAUD_RATE = [ - CUSTOM_BAUD_RATE_REF, - 50, - 75, - 110, - 134, - 150, - 200, - 300, - 600, - 1200, - 1800, - 2400, - 4800, - 9600, - 19200, - 38400, - 57600, - 115200, - 230400, - 460800, - 500000, - 576000, - 921600, - 1000000, - 1152000, - 1500000, - 2000000, - 2500000, - 3000000, - 3500000, - 4000000, -]; -const DATA_BITS: number[] = [8, 7, 6, 5]; -const FLOW_CONTROL = [ - { value: 0, name: 'None' }, - { value: 1, name: 'Hardware' }, - { value: 2, name: 'Software' }, -]; -const PARITY = [ - { value: 0, name: 'None' }, - { value: 1, name: 'Odd' }, - { value: 2, name: 'Even' }, -]; -const STOP_BITS = [1, 2]; -const EXCLUSIVE = [ - { value: true, name: 'Yes (default)' }, - { value: false, name: 'No' }, -]; -const DELAY = [ - { value: 0, name: 'No delay (default)' }, - { value: 10, name: '10 ms' }, - { value: 20, name: '20 ms' }, - { value: 30, name: '30 ms' }, - { value: 40, name: '40 ms' }, - { value: 50, name: '50 ms' }, -]; - -export class State implements Destroy { - public ports: string[] = []; - public changed: Subject = new Subject(); - public baudRateProxy: number | string = 115200; - public loaded: boolean = false; - - public BAUD_RATE = BAUD_RATE; - public DATA_BITS = DATA_BITS; - public FLOW_CONTROL = FLOW_CONTROL; - public PARITY = PARITY; - public STOP_BITS = STOP_BITS; - public EXCLUSIVE = EXCLUSIVE; - public DELAY = DELAY; - - protected timer: number = -1; - protected states: Map = new Map(); - protected prev: string = ''; - - constructor( - public readonly action: Action, - public readonly configuration: Stream.Serial.Configuration, - ) { - this.history().load(); - } - - public destroy(): void { - // Having method "destroy()" is requirement of session's storage - } - - public scan(): { - start(): void; - stop(): void; - } { - const logger = scope.getLogger('SerialPorts Scanner'); - return { - start: (): void => { - function isSame(prev: string[], current: string[]): boolean { - return prev.join(';') === current.join(';'); - } - this.scan().stop(); - bridge - .ports() - .list() - .then((ports: string[]) => { - this.loaded = true; - if (!isSame(this.ports.slice(1), ports)) { - this.ports = ports; - } - this.changed.emit(); - // if (this.ports.includes(this.configuration.configuration.path)) { - // return; - // } - // this.configuration.configuration.path = - // this.ports[0] === undefined ? '' : this.ports[0]; - // this.configuration.configuration.path !== '' && - // this.history().restore(this.configuration.configuration.path); - // this.prev = this.configuration.configuration.path; - // this.changed.emit(); - }) - .catch((err: Error) => { - logger.error(`Fail to update ports list due error: ${err.message}`); - }) - .finally(() => { - this.timer = setTimeout( - () => { - this.scan().start(); - }, - this.ports.length === 0 - ? NOPORTS_RESCAN_PORTS_DURATION_MS - : REGULAR_RESCAN_PORTS_DURATION_MS, - ) as unknown as number; - }); - }, - stop: (): void => { - clearTimeout(this.timer); - }, - }; - } - - public isEmpty(): boolean { - return this.ports.length === 0; - } - - public history(): { - update(path: string): void; - restore(path: string): void; - load(): void; - save(): void; - } { - const logger = scope.getLogger('SerialPorts Settings History'); - return { - update: (path: string): void => { - if (this.prev !== '') { - this.states.set(this.prev, this.configuration.configuration); - } - this.history().restore(path); - this.prev = path; - this.history().save(); - this.changed.emit(); - }, - restore: (path: string): void => { - const state = this.states.get(path); - this.configuration.overwrite( - state === undefined ? Stream.Serial.Configuration.initial() : state, - ); - this.baudRateProxtUpdate(); - this.configuration.configuration.path = path; - }, - load: (): void => { - bridge - .storage(SERIAL_PORT_SETTINGS_STORAGE) - .read() - .then((content: string) => { - if (content === '') { - return; - } - try { - const map = JSON.parse(content); - if (!(map instanceof Array)) { - logger.warn(`Invalid format of history`); - return; - } - this.states.clear(); - map.forEach((pair) => { - if (pair instanceof Array && pair.length === 2) { - this.states.set(pair[0], pair[1]); - } - }); - this.configuration.configuration.path !== '' && - this.history().restore(this.configuration.configuration.path); - } catch (e) { - logger.warn(`Fail to parse history: ${error(e)}`); - } - }) - .catch((err: Error) => { - logger.warn(`Fail to get history: ${err.message}`); - }); - }, - save: (): void => { - const map: [string, Stream.Serial.IConfiguration][] = []; - this.states.forEach((value, key) => { - map.push([key, value]); - }); - bridge - .storage(SERIAL_PORT_SETTINGS_STORAGE) - .write(JSON.stringify(map)) - .catch((err: Error) => { - logger.warn(`Fail to save history: ${err.message}`); - }); - }, - }; - } - - public isBoudRateCustom(): boolean { - return this.baudRateProxy === CUSTOM_BAUD_RATE_REF; - } - - public baudRateChange(): void { - this.configuration.configuration.baud_rate = - typeof this.baudRateProxy === 'string' - ? this.configuration.configuration.baud_rate - : this.baudRateProxy; - this.configuration.configuration.baud_rate = - typeof this.configuration.configuration.baud_rate === 'string' - ? parseInt(this.configuration.configuration.baud_rate, 10) - : this.configuration.configuration.baud_rate; - if ( - isNaN(this.configuration.configuration.baud_rate) || - !isFinite(this.configuration.configuration.baud_rate) - ) { - this.configuration.configuration.baud_rate = - Stream.Serial.Configuration.initial().baud_rate; - } - this.changed.emit(); - } - - public defaluts(): void { - const path = this.configuration.configuration.path; - this.configuration.overwrite(Stream.Serial.Configuration.initial()); - this.configuration.configuration.path = path; - this.states.set(this.configuration.configuration.path, this.configuration.configuration); - this.baudRateProxtUpdate(); - this.history().save(); - this.changed.emit(); - } - - protected baudRateProxtUpdate(): void { - if ( - this.BAUD_RATE.find((r) => r == this.configuration.configuration.baud_rate) === - undefined - ) { - this.baudRateProxy = CUSTOM_BAUD_RATE_REF; - } else { - this.baudRateProxy = this.configuration.configuration.baud_rate; - } - } -} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/states/tcp.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/states/tcp.ts deleted file mode 100644 index 133c2bdd3..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/states/tcp.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Destroy } from '@platform/types/env/types'; -import { Action } from '@ui/tabs/observe/action'; - -import * as Errors from '../bases/tcp/error'; -import * as Stream from '@platform/types/observe/origin/stream/index'; - -export class State implements Destroy { - public errors: { - address: Errors.ErrorState; - }; - - constructor( - public readonly action: Action, - public readonly configuration: Stream.TCP.Configuration, - ) { - this.errors = { - address: new Errors.ErrorState(Errors.Field.address, () => { - // this.update(); - }), - }; - } - - public destroy(): void { - // Having method "destroy()" is requirement of session's storage - } - - public isValid(): boolean { - if (!this.errors.address.isValid()) { - return false; - } - return true; - } - - public drop() { - this.configuration.configuration.bind_addr = Stream.TCP.Configuration.initial().bind_addr; - } - -} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/states/udp.ts b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/states/udp.ts deleted file mode 100644 index 6aa9fd202..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/states/udp.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Destroy } from '@platform/types/env/types'; -import { Action } from '../../../../../action'; - -import * as Errors from '../bases/udp/error'; -import * as Stream from '@platform/types/observe/origin/stream/index'; - -const MULTICAST_ADDR = '255.255.255.255'; -const MULTUCAST_INTERFACE = '0.0.0.0'; - -export class State implements Destroy { - public errors: { - address: Errors.ErrorState; - }; - - constructor( - public readonly action: Action, - public readonly configuration: Stream.UDP.Configuration, - ) { - this.errors = { - address: new Errors.ErrorState(Errors.Field.bindingAddress, () => { - // this.update(); - }), - }; - } - - public destroy(): void { - // Having method "destroy()" is requirement of session's storage - } - - public drop() { - this.configuration.configuration.bind_addr = Stream.UDP.Configuration.initial().bind_addr; - this.configuration.configuration.multicast = Stream.UDP.Configuration.initial().multicast; - } - - public addMulticast() { - this.configuration.configuration.multicast.push({ - multiaddr: MULTICAST_ADDR, - interface: MULTUCAST_INTERFACE, - }); - } - - public removeMulticast(index: number) { - index > -1 && this.configuration.configuration.multicast.splice(index, 1); - } -} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/styles.less b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/styles.less deleted file mode 100644 index 51a34a89c..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/styles.less +++ /dev/null @@ -1,19 +0,0 @@ -@import '../../../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - & mat-form-field.full-width { - display: block; - } - & mat-form-field.inline { - margin-right: 12px; - } - & button { - position: relative; - display: inline-block; - margin: 6px 0 6px 6px; - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/template.html b/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/template.html deleted file mode 100644 index effbaf0d6..000000000 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/template.html +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/component.ts b/application/client/src/app/ui/tabs/observe/parsers/extra/component.ts deleted file mode 100644 index a70358b24..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Component, ChangeDetectorRef, Input, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { State } from '../state'; - -@Component({ - selector: 'app-el-parser-extra', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class ParserExtraConfiguration extends ChangesDetector implements AfterContentInit { - @Input() state!: State; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - this.state.bind(this); - } -} -export interface ParserExtraConfiguration extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/module.ts b/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/module.ts deleted file mode 100644 index 760ef5b98..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/module.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatCardModule } from '@angular/material/card'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatChipsModule } from '@angular/material/chips'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatIconModule } from '@angular/material/icon'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; -import { MatListModule } from '@angular/material/list'; -import { MatTableModule } from '@angular/material/table'; -import { MatSortModule } from '@angular/material/sort'; - -import { DltExtraConfiguration } from './component'; -import { DltExtraConfigurationStructure } from './structure/component'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - MatButtonModule, - MatCardModule, - MatDividerModule, - MatProgressBarModule, - MatChipsModule, - MatIconModule, - MatFormFieldModule, - MatSelectModule, - MatListModule, - MatTableModule, - MatSortModule, - ], - declarations: [DltExtraConfiguration, DltExtraConfigurationStructure], - exports: [DltExtraConfiguration], - bootstrap: [DltExtraConfiguration, DltExtraConfigurationStructure], -}) -export class DltExtraConfigurationModule {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/state.ts b/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/state.ts deleted file mode 100644 index 9067f1498..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/state.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { State as Base } from '../../state'; -import { Section } from './structure/section'; -import { Summary } from './summary'; -import { StatEntity } from './structure/statentity'; -import { getTypedProp } from '@platform/env/obj'; -import { DltStatisticInfo, DltLevelDistribution } from '@platform/types/bindings'; - -import * as Dlt from '@platform/types/observe/parser/dlt'; - -export const ENTITIES = { - app_ids: 'app_ids', - context_ids: 'context_ids', - ecu_ids: 'ecu_ids', -}; - -export const NAMES: { [key: string]: string } = { - [ENTITIES.app_ids]: 'Applications', - [ENTITIES.context_ids]: 'Contexts', - [ENTITIES.ecu_ids]: 'ECUs', -}; -export class State extends Base { - public structure: Section[] = []; - public stat: DltStatisticInfo | undefined; - public summary: { - total: Summary; - selected: Summary; - } = { - total: new Summary(), - selected: new Summary(), - }; - - protected files(): string[] { - const files = this.observe.origin.files(); - if (files === undefined) { - throw new Error( - `Extra settings of DLT parser are available only for File and Concat origins`, - ); - } - return typeof files === 'string' ? [files] : files; - } - - protected filters(): Dlt.IFilters | undefined { - const parser = this.observe.parser.as(Dlt.Configuration); - if (parser === undefined) { - throw new Error(`Observe object uses not ${Dlt.Configuration.alias()} parser.`); - } - return parser.configuration.filter_config; - } - - public isStatLoaded(): boolean { - return this.stat !== undefined; - } - - public getSelectedEntities(): StatEntity[] { - let selected: StatEntity[] = []; - this.structure.forEach((section) => { - selected = selected.concat(section.getSelected()); - }); - return selected; - } - - public struct(): { - load(): Promise; - build(preselection?: Dlt.IFilters): void; - filter(value: string): void; - supported(): boolean; - } { - return { - supported: (): boolean => { - const parser = this.observe.parser.as(Dlt.Configuration); - return parser === undefined ? false : parser.configuration.with_storage_header; - }, - load: (): Promise => { - if (!this.struct().supported()) { - return Promise.resolve(); - } - return this.ref - .ilc() - .services.system.bridge.dlt() - .stat(this.files()) - .then((stat) => { - // this.tab.setTitle( - // this.files.length === 1 - // ? this.files[0].name - // : `${this.files.length} DLT files`, - // ); - this.stat = stat; - this.struct().build(this.filters()); - this.ref.detectChanges(); - }); - }, - build: (preselection?: Dlt.IFilters): void => { - if (!this.struct().supported()) { - return; - } - if (this.stat === undefined) { - return; - } - const stat = this.stat; - const structure: Section[] = []; - ['app_ids', 'context_ids', 'ecu_ids'].forEach((key: string) => { - const content: Array<[string, DltLevelDistribution]> = getTypedProp< - DltStatisticInfo, - Array<[string, DltLevelDistribution]> - >(stat, key); - const entities: StatEntity[] = content.map((record) => { - const entity = new StatEntity(record[0], key, record[1], this.matcher); - if ( - preselection !== undefined && - (preselection as any)[key] !== undefined - ) { - if ( - ((preselection as any)[key] as string[]).indexOf(record[0]) !== -1 - ) { - entity.select(); - } - } - return entity; - }); - structure.push(new Section(key, NAMES[key]).fill(entities)); - }); - this.structure = structure; - this.buildSummary().all(); - }, - filter: (value: string): void => { - if (!this.struct().supported()) { - return; - } - this.matcher.search(value); - this.structure.forEach((structure) => { - structure.entities.sort( - (a: StatEntity, b: StatEntity) => b.getScore() - a.getScore(), - ); - structure.update.emit(); - }); - }, - }; - } - - public buildSummary(): { - total(): void; - selected(): void; - all(): void; - } { - return { - total: (): void => { - this.summary.total.reset(); - this.structure.forEach((structure) => { - structure.entities.forEach((entity) => { - this.summary.total.inc(entity); - }); - }); - }, - selected: (): void => { - this.summary.selected.reset(); - this.structure.forEach((structure) => { - structure.entities.forEach((entity) => { - entity.selected && this.summary.selected.inc(entity); - }); - }); - const conf = this.observe.parser.as(Dlt.Configuration); - if (conf === undefined) { - return; - } - conf.setDefaultsFilterConfig(); - let app_id_count = 0; - let context_id_count = 0; - this.structure.forEach((structure) => { - if (structure.key === ENTITIES.app_ids) { - app_id_count = structure.entities.length; - } else if (structure.key === ENTITIES.context_ids) { - context_id_count = structure.entities.length; - } - const selected = structure.getSelected().map((f) => f.id); - if (selected.length === 0) { - (conf.configuration.filter_config as any)[structure.key] = undefined; - } else { - (conf.configuration.filter_config as any)[structure.key] = selected; - } - }); - if (conf.configuration.filter_config) { - conf.configuration.filter_config.app_id_count = app_id_count; - conf.configuration.filter_config.context_id_count = context_id_count; - } - }, - all: (): void => { - this.buildSummary().total(); - this.buildSummary().selected(); - }, - }; - } -} diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/statentity.ts b/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/statentity.ts deleted file mode 100644 index fb2961ffc..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/statentity.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { DltLevelDistribution } from '@platform/types/bindings'; -import { Matchee } from '@module/matcher'; - -import * as wasm from '@loader/wasm'; - -export class StatEntity extends Matchee { - public selected: boolean = false; - public id: string; - public parent: string; - public non_log: number; - public log_fatal: number; - public log_error: number; - public log_warning: number; - public log_info: number; - public log_debug: number; - public log_verbose: number; - public log_invalid: number; - - constructor(id: string, parent: string, from: DltLevelDistribution, matcher: wasm.Matcher) { - super(matcher, { id: id }); - this.id = id; - this.parent = parent; - this.non_log = from.non_log; - this.log_fatal = from.log_fatal; - this.log_error = from.log_error; - this.log_warning = from.log_warning; - this.log_info = from.log_info; - this.log_debug = from.log_debug; - this.log_verbose = from.log_verbose; - this.log_invalid = from.log_invalid; - } - - public html(): string { - const html: string | undefined = this.getHtmlOf('html_id'); - return html === undefined ? this.id : html; - } - - public hash(): string { - return `${this.parent}-${this.id}`; - } - - public equal(entity: StatEntity): boolean { - return entity.hash() === this.hash(); - } - - public toggle() { - this.selected = !this.selected; - } - - public select() { - this.selected = true; - } - - public unselect() { - this.selected = false; - } - - public hidden(): boolean { - return this.getScore() === 0; - } -} diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/template.html b/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/template.html deleted file mode 100644 index 605dd3223..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/structure/template.html +++ /dev/null @@ -1,62 +0,0 @@ -

{{section.name}}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ID - - FATAL - {{element.log_fatal}} - ERROR - {{element.log_error}} - WARN - {{element.log_warning}} - DEBUG - {{element.log_debug}} - INFO - {{element.log_info}} - VERB - {{element.log_verbose}} - NON - {{element.non_log}}
\ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/styles.less b/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/styles.less deleted file mode 100644 index e813d7e1d..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/styles.less +++ /dev/null @@ -1,116 +0,0 @@ -@import '../../../../../styles/variables.less'; - - -:host { - position: absolute; - display: block; - top: 0; - left: 0; - width: 100%; - height: 100%; - overflow: hidden; - & p.error { - color: var(--scheme-color-error-light); - text-align: center; - padding: 64px 0; - } - & app-transport { - margin-top: 12px; - } - & div.controlls { - text-align: right; - } - & h1 { - font-size: 14px; - } - & button { - position: relative; - display: inline-block; - margin: 6px 0 6px 6px; - } - & div.left { - position: absolute; - display: flex; - flex-direction: column; - top: 0; - bottom: 0; - left: 0; - right: 330px; - overflow: hidden; - justify-content: stretch; - justify-items: stretch; - & div.filter { - position: absolute; - z-index: 1000; - padding: 6px 12px; - background: var(--scheme-color-warning-light); - top: 6px; - right: 6px; - box-shadow: 3px 3px 3px rgb(0 0 0 / 40%); - border-radius: 3px; - } - & .fill { - overflow: auto; - } - & .section { - padding-top: 12px; - & mat-divider { - margin-top: -12px; - } - & .mat-standard-chip{ - min-height: 24px; - } - } - } - & div.right { - position: absolute; - width: 320px; - right: 6px; - bottom: 0; - top: 0; - overflow-y: auto; - overflow-x: hidden; - display: flex; - flex-direction: column; - & mat-divider.fibex-divider { - margin-top: 12px; - } - & div.fibex-controlls{ - position: relative; - width: 100%; - margin-top: 19px; - text-align: right; - } - & div.spacer { - flex: auto; - } - & app-tabs-source-actions { - position: sticky; - bottom: 0; - z-index: 1; - box-shadow: 0px 0px 16px rgba(0,0,0,0.4); - } - } - & .table-fill { - width: 100%; - } - & .info { - & .caption { - text-align: left; - padding-right: 12px; - } - & .value { - text-align: left; - } - } - - & div.progress { - margin-top: 24px; - & p { - text-align: center; - } - } - & .mat-progress-bar-fill::after { - background-color: var(--scheme-color-accent); - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/summary.ts b/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/summary.ts deleted file mode 100644 index 194eabc18..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/summary.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { StatEntity } from './structure/statentity'; - -export class Summary { - public fatal: number = 0; - public error: number = 0; - public warning: number = 0; - public info: number = 0; - public debug: number = 0; - public verbose: number = 0; - public invalid: number = 0; - public total: number = 0; - public count: number = 0; - - private _loaded: boolean = false; - - public isLoaded(): boolean { - return this._loaded; - } - - public inc(entity: StatEntity) { - this.fatal += entity.log_fatal; - this.error += entity.log_error; - this.warning += entity.log_warning; - this.info += entity.log_info; - this.debug += entity.log_debug; - this.verbose += entity.log_verbose; - this.invalid += entity.log_invalid; - this.total += - entity.log_fatal + - entity.log_error + - entity.log_warning + - entity.log_info + - entity.log_debug + - entity.log_verbose + - entity.log_invalid; - this.count += 1; - } - - public reset() { - this._loaded = true; - this.fatal = 0; - this.error = 0; - this.warning = 0; - this.info = 0; - this.debug = 0; - this.verbose = 0; - this.invalid = 0; - this.total = 0; - this.count = 0; - } -} diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/template.html b/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/template.html deleted file mode 100644 index 122c91d51..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/dlt/template.html +++ /dev/null @@ -1,67 +0,0 @@ - - -

- Cannot load statistics information because of an error: {{error}} -

- -
- -

Scanning DLT structure...

-
-
-

- DLT structure scanning isn't supported for this type of source -

-
-
- -

- Summary ({{state.summary.total.count}} / {{state.summary.selected.count}}) -

- - - - - - - - - - - - - - - - - - - - - - - -
Fatal - {{state.summary.total.fatal}}
{{state.summary.selected.fatal}} -
Error - {{state.summary.total.error}}
{{state.summary.selected.error}} -
Debug - {{state.summary.total.debug}}
{{state.summary.selected.debug}} -
Info - {{state.summary.total.info}}
{{state.summary.selected.info}} -
Verbose - {{state.summary.total.verbose}}
{{state.summary.selected.verbose}} -
Invalid - {{state.summary.total.invalid}}
{{state.summary.selected.invalid}} -
Total - {{state.summary.total.total}} / {{state.summary.selected.total}} -
-
- -
-
diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/module.ts b/application/client/src/app/ui/tabs/observe/parsers/extra/module.ts deleted file mode 100644 index 311c76473..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { DltExtraConfigurationModule } from './dlt/module'; -import { SomeIpExtraConfigurationModule } from './someip/module'; -import { ParserExtraConfiguration } from './component'; -@NgModule({ - imports: [CommonModule, DltExtraConfigurationModule, SomeIpExtraConfigurationModule], - declarations: [ParserExtraConfiguration], - exports: [ - ParserExtraConfiguration, - DltExtraConfigurationModule, - SomeIpExtraConfigurationModule, - ], - bootstrap: [ParserExtraConfiguration], -}) -export class ParserExtraConfigurationModule {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/someip/component.ts b/application/client/src/app/ui/tabs/observe/parsers/extra/someip/component.ts deleted file mode 100644 index b3419cc4e..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/someip/component.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Component, ChangeDetectorRef, Input, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { State } from './state'; -import { Observe } from '@platform/types/observe'; - -@Component({ - selector: 'app-el-someip-extra', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class SomeIpExtraConfiguration extends ChangesDetector implements AfterContentInit { - @Input() observe!: Observe; - - protected state!: State; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - this.state = new State(this.observe); - this.state.bind(this); - } -} -export interface SomeIpExtraConfiguration extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/someip/module.ts b/application/client/src/app/ui/tabs/observe/parsers/extra/someip/module.ts deleted file mode 100644 index c02e703ca..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/someip/module.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatCardModule } from '@angular/material/card'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatChipsModule } from '@angular/material/chips'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatIconModule } from '@angular/material/icon'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; -import { MatListModule } from '@angular/material/list'; - -import { SomeIpExtraConfiguration } from './component'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - MatButtonModule, - MatCardModule, - MatDividerModule, - MatProgressBarModule, - MatChipsModule, - MatIconModule, - MatFormFieldModule, - MatSelectModule, - MatListModule, - ], - declarations: [SomeIpExtraConfiguration], - exports: [SomeIpExtraConfiguration], - bootstrap: [SomeIpExtraConfiguration], -}) -export class SomeIpExtraConfigurationModule {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/someip/state.ts b/application/client/src/app/ui/tabs/observe/parsers/extra/someip/state.ts deleted file mode 100644 index 4579f844c..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/someip/state.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { State as Base } from '../../state'; - -export class State extends Base {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/someip/styles.less b/application/client/src/app/ui/tabs/observe/parsers/extra/someip/styles.less deleted file mode 100644 index 4bd77884a..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/someip/styles.less +++ /dev/null @@ -1,111 +0,0 @@ -@import '../../../../../styles/variables.less'; - - -:host { - position: absolute; - display: block; - top: 0; - left: 0; - width: 100%; - height: 100%; - overflow: hidden; - & app-transport { - margin-top: 12px; - } - & div.controlls { - text-align: right; - } - & h1 { - font-size: 14px; - } - & button { - position: relative; - display: inline-block; - margin: 6px 0 6px 6px; - } - & div.left { - position: absolute; - display: flex; - flex-direction: column; - top: 0; - bottom: 0; - left: 0; - right: 330px; - overflow: hidden; - justify-content: stretch; - justify-items: stretch; - & div.filter { - position: absolute; - z-index: 1000; - padding: 6px 12px; - background: var(--scheme-color-warning-light); - top: 6px; - right: 6px; - box-shadow: 3px 3px 3px rgb(0 0 0 / 40%); - border-radius: 3px; - } - & .fill { - overflow: auto; - } - & .section { - padding-top: 12px; - & mat-divider { - margin-top: -12px; - } - & .mat-standard-chip{ - min-height: 24px; - } - } - } - & div.right { - position: absolute; - width: 320px; - right: 6px; - bottom: 0; - top: 0; - overflow-y: auto; - overflow-x: hidden; - display: flex; - flex-direction: column; - & mat-divider.fibex-divider { - margin-top: 12px; - } - & div.fibex-controlls{ - position: relative; - width: 100%; - margin-top: 19px; - text-align: right; - } - & div.spacer { - flex: auto; - } - & app-tabs-source-actions { - position: sticky; - bottom: 0; - z-index: 1; - box-shadow: 0px 0px 16px rgba(0,0,0,0.4); - } - } - & .table-fill { - width: 100%; - } - & .info { - & .caption { - text-align: left; - padding-right: 12px; - } - & .value { - text-align: left; - } - } - - & div.progress { - margin-top: 24px; - & p { - text-align: center; - } - } - & .mat-progress-bar-fill::after { - background-color: var(--scheme-color-accent); - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/someip/template.html b/application/client/src/app/ui/tabs/observe/parsers/extra/someip/template.html deleted file mode 100644 index 139597f9c..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/someip/template.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/styles.less b/application/client/src/app/ui/tabs/observe/parsers/extra/styles.less deleted file mode 100644 index be92722fb..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/styles.less +++ /dev/null @@ -1,111 +0,0 @@ -@import '../../../../styles/variables.less'; - - -:host { - position: absolute; - display: block; - top: 0; - left: 0; - width: 100%; - height: 100%; - overflow: hidden; - & app-transport { - margin-top: 12px; - } - & div.controlls { - text-align: right; - } - & h1 { - font-size: 14px; - } - & button { - position: relative; - display: inline-block; - margin: 6px 0 6px 6px; - } - & div.left { - position: absolute; - display: flex; - flex-direction: column; - top: 0; - bottom: 0; - left: 0; - right: 330px; - overflow: hidden; - justify-content: stretch; - justify-items: stretch; - & div.filter { - position: absolute; - z-index: 1000; - padding: 6px 12px; - background: var(--scheme-color-warning-light); - top: 6px; - right: 6px; - box-shadow: 3px 3px 3px rgb(0 0 0 / 40%); - border-radius: 3px; - } - & .fill { - overflow: auto; - } - & .section { - padding-top: 12px; - & mat-divider { - margin-top: -12px; - } - & .mat-standard-chip{ - min-height: 24px; - } - } - } - & div.right { - position: absolute; - width: 320px; - right: 6px; - bottom: 0; - top: 0; - overflow-y: auto; - overflow-x: hidden; - display: flex; - flex-direction: column; - & mat-divider.fibex-divider { - margin-top: 12px; - } - & div.fibex-controlls{ - position: relative; - width: 100%; - margin-top: 19px; - text-align: right; - } - & div.spacer { - flex: auto; - } - & app-tabs-source-actions { - position: sticky; - bottom: 0; - z-index: 1; - box-shadow: 0px 0px 16px rgba(0,0,0,0.4); - } - } - & .table-fill { - width: 100%; - } - & .info { - & .caption { - text-align: left; - padding-right: 12px; - } - & .value { - text-align: left; - } - } - - & div.progress { - margin-top: 24px; - & p { - text-align: center; - } - } - & .mat-progress-bar-fill::after { - background-color: var(--scheme-color-accent); - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/parsers/extra/template.html b/application/client/src/app/ui/tabs/observe/parsers/extra/template.html deleted file mode 100644 index ea14b75d8..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/extra/template.html +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/component.ts b/application/client/src/app/ui/tabs/observe/parsers/general/component.ts deleted file mode 100644 index 71e961f33..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Component, ChangeDetectorRef, Input, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { State } from '../state'; -import { State as GlobalState } from '../../state'; - -@Component({ - selector: 'app-el-parser-general', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class ParserGeneralConfiguration extends ChangesDetector implements AfterContentInit { - @Input() state!: State; - @Input() global!: GlobalState; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - this.state.bind(this); - } -} -export interface ParserGeneralConfiguration extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/dlt/component.ts b/application/client/src/app/ui/tabs/observe/parsers/general/dlt/component.ts deleted file mode 100644 index 2c97a1320..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/dlt/component.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { - Component, - ChangeDetectorRef, - Input, - AfterContentInit, - AfterViewInit, -} from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { bytesToStr, timestampToUTC } from '@env/str'; -import { State } from './state'; -import { Observe } from '@platform/types/observe'; - -@Component({ - selector: 'app-el-dlt-general', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class DltGeneralConfiguration - extends ChangesDetector - implements AfterContentInit, AfterViewInit -{ - @Input() observe!: Observe; - - protected state!: State; - - public bytesToStr = bytesToStr; - public timestampToUTC = timestampToUTC; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - this.state = new State(this.observe); - this.state.bind(this); - } - - public ngAfterViewInit(): void { - this.state - .load() - .then(() => { - this.detectChanges(); - }) - .catch((err: Error) => { - this.log().error(`Fail to restore configuration with: ${err.message}`); - }); - } -} -export interface DltGeneralConfiguration extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/dlt/module.ts b/application/client/src/app/ui/tabs/observe/parsers/general/dlt/module.ts deleted file mode 100644 index c308db8a6..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/dlt/module.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatCardModule } from '@angular/material/card'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatChipsModule } from '@angular/material/chips'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatIconModule } from '@angular/material/icon'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; -import { MatListModule } from '@angular/material/list'; - -import { DltGeneralConfiguration } from './component'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - MatButtonModule, - MatCardModule, - MatDividerModule, - MatProgressBarModule, - MatChipsModule, - MatIconModule, - MatFormFieldModule, - MatSelectModule, - MatListModule, - ], - declarations: [DltGeneralConfiguration], - exports: [DltGeneralConfiguration], - bootstrap: [DltGeneralConfiguration], -}) -export class DltGeneralConfigurationModule {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/dlt/state.ts b/application/client/src/app/ui/tabs/observe/parsers/general/dlt/state.ts deleted file mode 100644 index 56e340b9a..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/dlt/state.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { File } from '@platform/types/files'; -import { Timezone } from '@elements/timezones/timezone'; -import { bridge } from '@service/bridge'; -import { components } from '@env/decorators/initial'; -import { State as Base } from '../../state'; -import { Observe } from '@platform/types/observe'; - -import * as Dlt from '@platform/types/observe/parser/dlt'; - -export class State extends Base { - public readonly LOG_LEVELS: { value: Dlt.LogLevel; caption: string }[] = [ - { value: Dlt.LogLevel.Fatal, caption: 'Fatal' }, - { value: Dlt.LogLevel.Error, caption: 'Error' }, - { value: Dlt.LogLevel.Warn, caption: 'Warn' }, - { value: Dlt.LogLevel.Info, caption: 'Info' }, - { value: Dlt.LogLevel.Debug, caption: 'Debug' }, - { value: Dlt.LogLevel.Verbose, caption: 'Verbose' }, - ]; - public fibex: File[] = []; - public timezone: Timezone | undefined; - public logLevel: Dlt.LogLevel = Dlt.LogLevel.Verbose; - - constructor(observe: Observe) { - super(observe); - } - - public async load(): Promise { - const conf = this.observe.parser.as(Dlt.Configuration); - if (conf === undefined) { - return; - } - const stored: string[] | string | undefined = conf.configuration.fibex_file_paths; - const paths: string[] = - stored === undefined - ? [] - : typeof stored === 'string' - ? [stored] - : stored.map((p) => p); - this.fibex = await bridge.files().getByPath(paths); - conf.configuration.filter_config !== undefined && - conf.configuration.filter_config.min_log_level !== undefined && - (this.logLevel = conf.configuration.filter_config.min_log_level); - const timezone = - conf.configuration.tz !== undefined ? Timezone.from(conf.configuration.tz) : undefined; - this.timezone = timezone instanceof Error ? undefined : timezone; - } - - public update(): State { - const conf = this.observe.parser.as(Dlt.Configuration); - if (conf === undefined) { - return this; - } - if (this.fibex.length !== 0) { - conf.configuration.fibex_file_paths = this.fibex.map((f) => f.filename); - } else { - conf.configuration.fibex_file_paths = undefined; - } - if (this.logLevel !== Dlt.LogLevel.Verbose) { - conf.setDefaultsFilterConfig(); - conf.configuration.filter_config!.min_log_level = this.logLevel; - } - conf.configuration.tz = this.timezone === undefined ? undefined : this.timezone.name; - return this; - } - - public addFibexFile() { - bridge - .files() - .select.custom('xml') - .then((files: File[]) => { - files = files.filter((added) => { - return ( - this.fibex.find((exist) => exist.filename === added.filename) === undefined - ); - }); - this.fibex = this.fibex.concat(files); - }) - .catch((err: Error) => { - this.ref.log().error(`Fail to open xml (fibex) file(s): ${err.message}`); - }) - .finally(() => { - this.update().ref.detectChanges(); - }); - } - - public removeFibex(file: File) { - this.fibex = this.fibex.filter((f) => f.filename !== file.filename); - this.update().ref.detectChanges(); - } - - public timezoneSelect() { - const subscription = this.ref - .ilc() - .services.ui.popup.open({ - component: { - factory: components.get('app-elements-timezone-selector'), - inputs: { - selected: (timezone: Timezone): void => { - if (timezone.name.toLowerCase().startsWith('utc')) { - this.timezone = undefined; - } else { - this.timezone = timezone; - } - this.update(); - this.ref.detectChanges(); - }, - }, - }, - closeOnKey: 'Escape', - width: 350, - uuid: 'app-elements-timezone-selector', - }) - .subjects.get() - .closed.subscribe(() => { - subscription.unsubscribe(); - }); - } -} diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/dlt/styles.less b/application/client/src/app/ui/tabs/observe/parsers/general/dlt/styles.less deleted file mode 100644 index d28051537..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/dlt/styles.less +++ /dev/null @@ -1,17 +0,0 @@ -@import '../../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - overflow: hidden; - & div.controlls { - text-align: right; - } - & button { - position: relative; - display: inline-block; - margin: 6px 0 6px 6px; - } -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/dlt/template.html b/application/client/src/app/ui/tabs/observe/parsers/general/dlt/template.html deleted file mode 100644 index 89e1ea8ab..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/dlt/template.html +++ /dev/null @@ -1,48 +0,0 @@ - - Log level - - - Select required level of logs - - - {{level.caption}} - - - - - - - Fibex - Attach fibex file (optional) - -
- - - {{file.name}} - - - -
- -
- -
-
-
- - Timezone - Select required timezone (optional). UTC is default - - - {{state.timezone.name}} ({{state.timezone.utc}}) - -
- -
-
-
diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/module.ts b/application/client/src/app/ui/tabs/observe/parsers/general/module.ts deleted file mode 100644 index 740831ef0..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/module.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { ErrorStateModule } from '../../error/module'; -import { DltGeneralConfigurationModule } from './dlt/module'; -import { SomeIpGeneralConfigurationModule } from './someip/module'; -import { TextGeneralConfigurationModule } from './text/module'; -import { ParserPluginGeneralConfigurationModule } from './plugin/module'; -import { ParserGeneralConfiguration } from './component'; - -@NgModule({ - imports: [ - CommonModule, - DltGeneralConfigurationModule, - SomeIpGeneralConfigurationModule, - TextGeneralConfigurationModule, - ParserPluginGeneralConfigurationModule, - ErrorStateModule, - ], - declarations: [ParserGeneralConfiguration], - exports: [ - ParserGeneralConfiguration, - DltGeneralConfigurationModule, - SomeIpGeneralConfigurationModule, - TextGeneralConfigurationModule, - ParserPluginGeneralConfigurationModule, - ErrorStateModule, - ], - bootstrap: [ParserGeneralConfiguration], -}) -export class ParserGeneralConfigurationModule {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/plugin/component.ts b/application/client/src/app/ui/tabs/observe/parsers/general/plugin/component.ts deleted file mode 100644 index e7192c6a2..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/plugin/component.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - Component, - Input, - ChangeDetectorRef, - AfterContentInit, - AfterViewInit, -} from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { Observe } from '@platform/types/observe'; -import { State } from './state'; - -@Component({ - selector: 'app-el-parser-plugin-general', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class ParserPluginGeneralConfiguration - extends ChangesDetector - implements AfterContentInit, AfterViewInit -{ - @Input() observe!: Observe; - @Input() path!: string | undefined; - - protected state!: State; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - ngAfterContentInit(): void { - this.state = new State(this.observe, this.path); - this.state.bind(this); - this.env().subscriber.register( - this.observe.parser.subscribe(() => { - if (!this.path) { - return; - } - this.state.setPath(this.path); - }), - ); - } - - public update(): void { - this.state.update(); - } - - ngAfterViewInit(): void { - this.state.init(); - this.detectChanges(); - } -} - -export interface ParserPluginGeneralConfiguration extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/plugin/module.ts b/application/client/src/app/ui/tabs/observe/parsers/general/plugin/module.ts deleted file mode 100644 index 12bf08c02..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/plugin/module.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { MatCardModule } from '@angular/material/card'; -import { MatDividerModule } from '@angular/material/divider'; - -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatChipsModule } from '@angular/material/chips'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; -import { MatListModule } from '@angular/material/list'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatInputModule } from '@angular/material/input'; -import { ConfigSchmasModule } from '@ui/tabs/observe/config-schema/module'; -import { ParserPluginGeneralConfiguration } from './component'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - MatButtonModule, - MatCardModule, - MatDividerModule, - MatChipsModule, - MatFormFieldModule, - MatSelectModule, - MatListModule, - ConfigSchmasModule, - MatCheckboxModule, - MatInputModule, - ], - declarations: [ParserPluginGeneralConfiguration], - exports: [ParserPluginGeneralConfiguration], - bootstrap: [ParserPluginGeneralConfiguration], -}) -export class ParserPluginGeneralConfigurationModule {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/plugin/state.ts b/application/client/src/app/ui/tabs/observe/parsers/general/plugin/state.ts deleted file mode 100644 index 7e7cf45f7..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/plugin/state.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { State as Base } from '../../state'; -import { Observe } from '@platform/types/observe'; -import { plugins } from '@service/plugins'; -import { getSafeFileName } from '@platform/types/files'; -import { Subject } from '@platform/env/subscription'; - -import * as Plugin from '@platform/types/observe/parser/plugin'; -import { - PluginEntity, - PluginConfigSchemaItem, - PluginConfigValue, -} from '@platform/types/bindings/plugins'; - -export class State extends Base { - public parsers: PluginEntity[] = []; - public path: string | undefined; - public selectedParser?: PluginEntity; - public selected: Subject = new Subject(); - - constructor(observe: Observe, path: string | undefined) { - super(observe); - this.path = path; - } - - public init() { - const conf = this.observe.parser.as(Plugin.Configuration); - if (conf === undefined) { - this.ref.log().error(`Currnet parser configuration must match plugin parser`); - return; - } - this.parsers = plugins - .list() - .preload() - .filter((p) => p.plugin_type === 'Parser'); - - if (this.parsers.length > 0) { - this.selectedParser = this.path - ? this.parsers.find((p) => p.dir_path == this.path) - : this.parsers[0]; - this.update(); - } - } - - public setPath(path: string) { - this.path = path; - if (this.parsers.length > 0) { - this.selectedParser = this.path - ? this.parsers.find((p) => p.dir_path == this.path) - : this.parsers[0]; - this.update(); - } - } - - /** - * Saves the given value to the tab configurations - * @param id - ID of the configuration entry - * @param value - Value of the configuration - */ - public saveConfig(id: string, value: PluginConfigValue) { - const conf = this.observe.parser.as(Plugin.Configuration); - if (conf === undefined) { - this.ref.log().error(`Currnet parser configuration must match plugin parser`); - return; - } - - const item = conf.configuration.plugin_configs.find((item) => item.id === id); - if (item !== undefined) { - item.value = value; - } else { - conf.configuration.plugin_configs.push({ id, value }); - } - } - - /** - * Updates the configuration data for the parser in the current tab - */ - public update() { - const conf = this.observe.parser.as(Plugin.Configuration); - if (conf === undefined) { - return; - } - - const pluginPath = this.selectedParser?.info.wasm_file_path ?? ''; - - if (conf.configuration.plugin_path !== pluginPath) { - conf.configuration.plugin_path = pluginPath; - // Clear configurations on plugin change. - conf.configuration.plugin_configs = []; - } - this.selectedParser && this.selected.emit(this.selectedParser); - } - - public getPluginTitle(parser: PluginEntity): string { - return parser.metadata?.title ?? getSafeFileName(parser.dir_path); - } - - public getPluginConfigs(parser?: PluginEntity): PluginConfigSchemaItem[] { - return parser?.info.config_schemas ?? []; - } -} diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/plugin/styles.less b/application/client/src/app/ui/tabs/observe/parsers/general/plugin/styles.less deleted file mode 100644 index 04f4051d5..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/plugin/styles.less +++ /dev/null @@ -1,8 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - position: relative; - display: block; - width: 100%; - overflow: hidden; -} diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/plugin/template.html b/application/client/src/app/ui/tabs/observe/parsers/general/plugin/template.html deleted file mode 100644 index e689845ed..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/plugin/template.html +++ /dev/null @@ -1,6 +0,0 @@ - - Configurations - - - - diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/someip/component.ts b/application/client/src/app/ui/tabs/observe/parsers/general/someip/component.ts deleted file mode 100644 index c446a4909..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/someip/component.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Component, ChangeDetectorRef, Input, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { bytesToStr } from '@env/str'; -import { State } from './state'; -import { Observe } from '@platform/types/observe'; - -@Component({ - selector: 'app-el-someip-general', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class SomeIpGeneralConfiguration extends ChangesDetector implements AfterContentInit { - @Input() observe!: Observe; - - protected state!: State; - - public bytesToStr = bytesToStr; - - constructor(cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - this.state = new State(this.observe); - this.state.bind(this); - } -} -export interface SomeIpGeneralConfiguration extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/someip/module.ts b/application/client/src/app/ui/tabs/observe/parsers/general/someip/module.ts deleted file mode 100644 index c7400a679..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/someip/module.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatCardModule } from '@angular/material/card'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatChipsModule } from '@angular/material/chips'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatIconModule } from '@angular/material/icon'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; -import { MatListModule } from '@angular/material/list'; - -import { SomeIpGeneralConfiguration } from './component'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - MatButtonModule, - MatCardModule, - MatDividerModule, - MatProgressBarModule, - MatChipsModule, - MatIconModule, - MatFormFieldModule, - MatSelectModule, - MatListModule, - ], - declarations: [SomeIpGeneralConfiguration], - exports: [SomeIpGeneralConfiguration], - bootstrap: [SomeIpGeneralConfiguration], -}) -export class SomeIpGeneralConfigurationModule {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/someip/state.ts b/application/client/src/app/ui/tabs/observe/parsers/general/someip/state.ts deleted file mode 100644 index 28a593ef4..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/someip/state.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { File } from '@platform/types/files'; -import { bridge } from '@service/bridge'; -import { State as Base } from '../../state'; - -import * as SomeIp from '@platform/types/observe/parser/someip'; - -export class State extends Base { - public fibex: File[] = []; - - public update(): State { - const conf = this.observe.parser.as(SomeIp.Configuration); - if (conf === undefined) { - return this; - } - if (this.fibex.length !== 0) { - conf.configuration.fibex_file_paths = this.fibex.map((f) => f.filename); - } else { - conf.configuration.fibex_file_paths = undefined; - } - return this; - } - - public addFibexFile() { - bridge - .files() - .select.custom('xml') - .then((files: File[]) => { - files = files.filter((added) => { - return ( - this.fibex.find((exist) => exist.filename === added.filename) === undefined - ); - }); - this.fibex = this.fibex.concat(files); - }) - .catch((err: Error) => { - this.ref.log().error(`Fail to open xml (fibex) file(s): ${err.message}`); - }) - .finally(() => { - this.update().ref.detectChanges(); - }); - } - - public removeFibex(file: File) { - this.fibex = this.fibex.filter((f) => f.filename !== file.filename); - this.update().ref.detectChanges(); - } -} diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/someip/styles.less b/application/client/src/app/ui/tabs/observe/parsers/general/someip/styles.less deleted file mode 100644 index 684961bca..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/someip/styles.less +++ /dev/null @@ -1,18 +0,0 @@ -@import '../../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - overflow: hidden; - & div.controlls { - text-align: right; - } - & button { - position: relative; - display: inline-block; - margin: 6px 0 6px 6px; - } - -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/someip/template.html b/application/client/src/app/ui/tabs/observe/parsers/general/someip/template.html deleted file mode 100644 index b25ae2497..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/someip/template.html +++ /dev/null @@ -1,24 +0,0 @@ - - Fibex - Attach fibex file (optional) - -
- - - {{file.name}} - - - -
- -
- -
-
-
- diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/template.html b/application/client/src/app/ui/tabs/observe/parsers/general/template.html deleted file mode 100644 index 41493effc..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/template.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/text/component.ts b/application/client/src/app/ui/tabs/observe/parsers/general/text/component.ts deleted file mode 100644 index 5b989063a..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/text/component.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Initial } from '@env/decorators/initial'; -import { Observe } from '@platform/types/observe'; - -@Component({ - selector: 'app-el-text-general', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Initial() -@Ilc() -export class TextGeneralConfiguration { - @Input() observe!: Observe; -} -export interface TextGeneralConfiguration extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/text/module.ts b/application/client/src/app/ui/tabs/observe/parsers/general/text/module.ts deleted file mode 100644 index ed5b0df2d..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/text/module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { MatCardModule } from '@angular/material/card'; -import { MatDividerModule } from '@angular/material/divider'; - -import { TextGeneralConfiguration } from './component'; - -@NgModule({ - imports: [CommonModule, MatCardModule, MatDividerModule], - declarations: [TextGeneralConfiguration], - exports: [TextGeneralConfiguration], - bootstrap: [TextGeneralConfiguration], -}) -export class TextGeneralConfigurationModule {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/text/styles.less b/application/client/src/app/ui/tabs/observe/parsers/general/text/styles.less deleted file mode 100644 index 2276da0bd..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/text/styles.less +++ /dev/null @@ -1,9 +0,0 @@ -@import '../../../../../styles/variables.less'; - - -:host { - position: relative; - display: block; - width: 100%; - overflow: hidden; -} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/observe/parsers/general/text/template.html b/application/client/src/app/ui/tabs/observe/parsers/general/text/template.html deleted file mode 100644 index de35a517d..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/general/text/template.html +++ /dev/null @@ -1,7 +0,0 @@ - - Plain text parser - -

This parser doesn't require any kind of configuration

-
-
- diff --git a/application/client/src/app/ui/tabs/observe/parsers/module.ts b/application/client/src/app/ui/tabs/observe/parsers/module.ts deleted file mode 100644 index 79fc31786..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { ParserGeneralConfigurationModule } from './general/module'; -import { ParserExtraConfigurationModule } from './extra/module'; - -@NgModule({ - imports: [CommonModule, ParserGeneralConfigurationModule, ParserExtraConfigurationModule], - declarations: [], - exports: [], - bootstrap: [], -}) -export class ParsersModule {} diff --git a/application/client/src/app/ui/tabs/observe/parsers/state.ts b/application/client/src/app/ui/tabs/observe/parsers/state.ts deleted file mode 100644 index afd24b19c..000000000 --- a/application/client/src/app/ui/tabs/observe/parsers/state.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { IlcInterface } from '@env/decorators/component'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { Observe } from '@platform/types/observe'; -import { Holder } from '@module/matcher'; - -export class State extends Holder { - protected ref!: IlcInterface & ChangesDetector; - - constructor(public readonly observe: Observe) { - super(); - } - - public bind(ref: IlcInterface & ChangesDetector) { - this.ref = ref; - } -} diff --git a/application/client/src/app/ui/tabs/observe/state.ts b/application/client/src/app/ui/tabs/observe/state.ts deleted file mode 100644 index a79f5e084..000000000 --- a/application/client/src/app/ui/tabs/observe/state.ts +++ /dev/null @@ -1,347 +0,0 @@ -import { IList, Observe, Parser } from '@platform/types/observe'; -import { IlcInterface } from '@env/decorators/component'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { Subjects, Subject, Subscriber } from '@platform/env/subscription'; -import { File } from '@platform/types/files'; -import { bytesToStr } from '@env/str'; -import { Action } from './action'; -import { TabControls } from '@service/session'; -import { Notification } from '@ui/service/notifications'; -import { Locker, Level } from '@ui/service/lockers'; -import { PluginEntity } from '@platform/types/bindings'; - -import * as StreamOrigin from '@platform/types/observe/origin/stream/index'; -import * as Origin from '@platform/types/observe/origin/index'; -import * as FileOrigin from '@platform/types/observe/origin/file'; -import * as ConcatOrigin from '@platform/types/observe/origin/concat'; -import * as Parsers from '@platform/types/observe/parser'; -import * as Streams from '@platform/types/observe/origin/stream/index'; -import { plugins } from '@service/plugins'; - -export interface IApi { - finish(observe: Observe): Promise; - cancel(): void; - tab(): TabControls; -} - -export interface IInputs { - observe: Observe; - api: IApi; -} - -export class WrappedParserRef { - constructor(protected readonly inner: Parser.Reference | PluginEntity) {} - - private as_embedded(): Parser.Reference | undefined { - if (typeof (this.inner as any)['alias'] === 'function') { - return this.inner as Parser.Reference; - } - return undefined; - } - - private as_plugin(): PluginEntity | undefined { - if (typeof (this.inner as any)['dir_path'] === 'string') { - return this.inner as PluginEntity; - } - return undefined; - } - - public desc(): IList { - const embedded = this.as_embedded(); - if (embedded !== undefined) { - return embedded.desc(); - } - const plugin = this.as_plugin(); - if (plugin !== undefined) { - return { - major: plugin.metadata.title, - minor: plugin.metadata.description - ? plugin.metadata.description - : plugin.metadata.title, - icon: undefined, - }; - } - throw new Error(`Fail to process ${JSON.stringify(this.inner)}`); - } - public alias(): Parser.Protocol | string { - const embedded = this.as_embedded(); - if (embedded !== undefined) { - return embedded.alias(); - } - const plugin = this.as_plugin(); - if (plugin !== undefined) { - return plugin.dir_path; - } - throw new Error(`Fail to process ${JSON.stringify(this.inner)}`); - } -} - -export class State extends Subscriber { - public parser: Parser.Protocol | string | undefined; - public parsers: { ref: WrappedParserRef; disabled: boolean }[] = []; - public streams: { ref: StreamOrigin.Reference; disabled: boolean }[] = []; - public file: File | undefined; - public concat: File[] | undefined; - public stream: StreamOrigin.Source | undefined; - public size: string | undefined; - public action: Action = new Action(); - public updates: Subjects<{ - parser: Subject; - stream: Subject; - }> = new Subjects({ - parser: new Subject(), - stream: new Subject(), - }); - - constructor( - protected readonly ref: IlcInterface & ChangesDetector & { api: IApi }, - public readonly observe: Observe, - ) { - super(); - this.update().stream(); - this.update().files(); - this.update().parser(); - this.update().validate(); - this.update().action(); - this.register( - this.action.subjects.get().updated.subscribe(() => { - this.ref.markChangesForCheck(); - }), - this.action.subjects.get().apply.subscribe(() => { - this.finish(); - }), - this.observe.subscribe(() => { - this.update().validate(); - this.update().action(); - }), - ); - } - - public destroy() { - this.updates.destroy(); - this.unsubscribe(); - } - - public finish() { - if (this.action.disabled) { - return; - } - this.action.applied(); - this.ref.api.finish(this.observe).catch((err: Error) => { - this.ref - .ilc() - .services.ui.lockers.lock( - new Locker(true, err.message).set().type(Level.error).spinner(false).end(), - { - closable: true, - closeOnKey: 'Escape', - closeOnBGClick: true, - }, - ); - this.ref.ilc().services.ui.notifications.store( - new Notification({ - message: err.message, - actions: [], - }), - ); - }); - } - - public cancel() { - this.ref.api.cancel(); - } - - public getParser(): { - embedded(): Parser.Protocol | undefined; - pluginPath(): string | undefined; - } { - return { - embedded: (): Parser.Protocol | undefined => { - if (!this.parser) { - return undefined; - } - const embedded = Parser.tryAsEmbedded(this.parser); - return embedded ? embedded : undefined; - }, - pluginPath: (): string | undefined => { - if (!this.parser) { - return undefined; - } - return Parser.tryAsEmbedded(this.parser) ? undefined : (this.parser as string); - }, - }; - } - - public update(): { - stream(): void; - files(): void; - parser(): void; - validate(): void; - action(): void; - } { - return { - stream: (): void => { - const prev = this.stream; - const nature = this.observe.origin.nature(); - if ( - nature instanceof Origin.File.Configuration || - nature instanceof Origin.Concat.Configuration || - nature instanceof Origin.Plugin.Configuration - ) { - this.streams = []; - this.stream = undefined; - } else { - this.streams = this.observe.parser.getSupportedStream().map((ref) => { - return { ref, disabled: false }; - }); - if (this.stream === undefined) { - this.stream = nature.alias(); - } else { - const current = this.stream; - this.stream = - current !== undefined && - this.streams.find((p) => p.ref.alias() === current) !== undefined - ? current - : this.streams[0].ref.alias(); - } - this.streams.push( - ...Streams.getAllRefs() - .filter( - (ref) => - this.streams.find((p) => p.ref.alias() === ref.alias()) === - undefined, - ) - .map((ref) => { - return { ref, disabled: true }; - }), - ); - } - this.ref.markChangesForCheck(); - prev !== this.stream && this.updates.get().stream.emit(); - }, - files: (): void => { - const instance = this.observe.origin.instance; - const files: string[] | undefined = - instance instanceof FileOrigin.Configuration - ? [instance.filename()] - : instance instanceof ConcatOrigin.Configuration - ? instance.files() - : undefined; - if (files === undefined) { - return; - } - this.ref - .ilc() - .services.system.bridge.files() - .getByPath(files) - .then((files: File[]) => { - this.size = bytesToStr( - files - .map((f) => f.stat.size) - .reduce((partialSum, a) => partialSum + a, 0), - ); - if (instance instanceof FileOrigin.Configuration) { - if (files.length !== 1) { - this.ref - .log() - .error( - `Expecting only 1 file stats. Has been gotten: ${files.length}`, - ); - return; - } - this.file = files[0]; - } else if (instance instanceof ConcatOrigin.Configuration) { - this.concat = files; - } - }) - .catch((err: Error) => { - this.ref - .log() - .error( - `Fail to get stats for files: ${files.join(', ')}: ${err.message}`, - ); - }) - .finally(() => { - this.ref.markChangesForCheck(); - }); - }, - parser: (): void => { - const current = this.parser; - this.parsers = [ - ...this.observe.origin - .getSupportedParsers() - .filter((ref) => ref.alias() !== Parser.Protocol.Plugin) - .map((ref) => { - return { ref: new WrappedParserRef(ref), disabled: false }; - }), - ...plugins - .list() - .preload() - .filter((ref) => ref.plugin_type === 'Parser') - .map((ref) => { - return { ref: new WrappedParserRef(ref), disabled: false }; - }), - ]; - this.parser = - current !== undefined && - this.parsers.find((p) => p.ref.alias() === current) !== undefined - ? current - : this.parsers[0].ref.alias(); - this.parsers.push( - ...Parsers.getAllRefs() - .filter( - (ref) => - this.parsers.find((p) => p.ref.alias() === ref.alias()) === - undefined && ref.inited(), - ) - .map((ref) => { - return { ref: new WrappedParserRef(ref), disabled: true }; - }), - ); - this.ref.markChangesForCheck(); - current !== this.parser && this.updates.get().parser.emit(); - }, - validate: (): void => { - const error = this.observe.validate(); - this.action.setDisabled(error instanceof Error); - }, - action: (): void => { - this.action.setCaption(this.observe.origin.desc().action); - }, - }; - } - - public change(): { - stream(): void; - parser(): void; - } { - return { - stream: (): void => { - if (this.stream === undefined) { - this.ref.log().error(`Stream cannot be changed, because it's undefined`); - return; - } - const instance = this.observe.origin.instance; - if (!(instance instanceof Origin.Stream.Configuration)) { - this.ref.log().error(`Stream cannot be changed, because origin isn't Stream`); - return; - } - instance.change(StreamOrigin.getByAlias(this.stream)); - this.updates.get().stream.emit(); - this.update().parser(); - }, - parser: (): void => { - if (this.parser === undefined) { - this.ref.log().error(`Parser cannot be changed, because it's undefined`); - return; - } - const alias = Parser.tryAsEmbedded(this.parser); - this.observe.parser.change( - Parser.getByAlias(alias ? alias : Parser.Protocol.Plugin), - ); - this.updates.get().parser.emit(); - this.update().stream(); - }, - }; - } -} diff --git a/application/client/src/app/ui/tabs/observe/template.html b/application/client/src/app/ui/tabs/observe/template.html deleted file mode 100644 index 7400d0a98..000000000 --- a/application/client/src/app/ui/tabs/observe/template.html +++ /dev/null @@ -1,35 +0,0 @@ -
-

{{state.file.name}}({{state.size}})

-

Concating:{{state.concat.length}}files({{state.size}})

-
-

Stream From:

- - - - {{stream.ref.desc().major}} - - - -
-
-

Used Parser:

- - - - {{parser.ref.desc().major}} - - - -
-
- - -
-
-
-
- - - -
-
diff --git a/application/client/src/app/ui/tabs/setup/component.ts b/application/client/src/app/ui/tabs/setup/component.ts new file mode 100644 index 000000000..07e2fb8c0 --- /dev/null +++ b/application/client/src/app/ui/tabs/setup/component.ts @@ -0,0 +1,88 @@ +import { + Component, + ChangeDetectorRef, + ChangeDetectionStrategy, + Input, + AfterViewInit, + AfterContentInit, + OnDestroy, +} from '@angular/core'; +import { Ilc, IlcInterface } from '@env/decorators/component'; +import { Initial } from '@env/decorators/initial'; +import { ChangesDetector } from '@ui/env/extentions/changes'; +import { State, IApi } from './state'; +import { SessionOrigin } from '@service/session/origin'; +import { session } from '@service/session'; + +@Component({ + selector: 'app-tabs-setup', + templateUrl: './template.html', + styleUrls: ['./styles.less'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +@Initial() +@Ilc() +export class SetupObserve + extends ChangesDetector + implements AfterViewInit, AfterContentInit, OnDestroy +{ + @Input() api!: IApi; + @Input() origin!: SessionOrigin; + /// Preselected parser + @Input() parser: string | undefined; + /// Preselected source + @Input() source: string | undefined; + + public state!: State; + + constructor(cdRef: ChangeDetectorRef) { + super(cdRef); + } + + public ngOnDestroy(): void { + this.state.destroy(); + } + + public ngAfterContentInit(): void { + this.state = new State(this.origin, this.parser, this.source); + } + + public ngAfterViewInit(): void { + this.env().subscriber.register( + this.state.subjects.get().parsers.subscribe(() => { + this.detectChanges(); + }), + this.state.subjects.get().sources.subscribe(() => { + this.detectChanges(); + }), + this.state.subjects.get().updated.subscribe(() => { + this.detectChanges(); + }), + this.state.subjects.get().errorStateChange.subscribe(() => { + this.detectChanges(); + }), + ); + } + + public onStart() { + const error = this.state.setSourceOrigin(this.origin); + if (error instanceof Error) { + this.log().debug(`Cannot start session, because: ${error.message}`); + return; + } + this.api + .finish(this.origin) + .then((uuid: string) => { + this.log().debug(`Session has been created: ${uuid}`); + }) + .catch((err: Error) => { + this.log().error(`Fail to create session: ${err.message}`); + }); + } + + public onCancel() { + this.api.cancel(); + } +} +export interface SetupObserve extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/tcp/module.ts b/application/client/src/app/ui/tabs/setup/module.ts similarity index 59% rename from application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/tcp/module.ts rename to application/client/src/app/ui/tabs/setup/module.ts index 408a3f389..7014f6941 100644 --- a/application/client/src/app/ui/tabs/observe/origin/stream/transport/setup/complete/tcp/module.ts +++ b/application/client/src/app/ui/tabs/setup/module.ts @@ -2,14 +2,13 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; -import { MatCardModule } from '@angular/material/card'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatIconModule } from '@angular/material/icon'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; -import { MatInputModule } from '@angular/material/input'; -import { Setup } from './component'; +import { MatCardModule } from '@angular/material/card'; + +import { SettingsSchemeModule } from '@elements/scheme/module'; +import { SetupObserve } from './component'; +import { SourceOriginComponent } from './origin/component'; @NgModule({ imports: [ @@ -17,15 +16,13 @@ import { Setup } from './component'; FormsModule, ReactiveFormsModule, MatButtonModule, - MatCardModule, - MatDividerModule, - MatProgressBarModule, - MatIconModule, MatFormFieldModule, MatSelectModule, - MatInputModule, + MatCardModule, + SettingsSchemeModule, ], - declarations: [Setup], - exports: [Setup] + declarations: [SetupObserve, SourceOriginComponent], + exports: [SetupObserve], + bootstrap: [SetupObserve, SourceOriginComponent], }) export class SetupModule {} diff --git a/application/client/src/app/ui/tabs/setup/origin/component.ts b/application/client/src/app/ui/tabs/setup/origin/component.ts new file mode 100644 index 000000000..583f450cf --- /dev/null +++ b/application/client/src/app/ui/tabs/setup/origin/component.ts @@ -0,0 +1,38 @@ +import { + Component, + ChangeDetectorRef, + ChangeDetectionStrategy, + Input, + AfterViewInit, + AfterContentInit, + OnDestroy, +} from '@angular/core'; +import { Ilc, IlcInterface } from '@env/decorators/component'; +import { Initial } from '@env/decorators/initial'; +import { ChangesDetector } from '@ui/env/extentions/changes'; +import { SessionOrigin } from '@service/session/origin'; + +@Component({ + selector: 'app-tabs-setup-origin', + templateUrl: './template.html', + styleUrls: ['./styles.less'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +@Initial() +@Ilc() +export class SourceOriginComponent implements AfterContentInit, OnDestroy { + @Input() origin!: SessionOrigin; + + public title: string = ''; + public description: string | undefined; + + public ngOnDestroy(): void {} + + public ngAfterContentInit(): void { + const description = this.origin.getDescription(); + this.title = description.title; + this.description = description.desctiption; + } +} +export interface SourceOriginComponent extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/setup/origin/styles.less b/application/client/src/app/ui/tabs/setup/origin/styles.less new file mode 100644 index 000000000..384287f1f --- /dev/null +++ b/application/client/src/app/ui/tabs/setup/origin/styles.less @@ -0,0 +1,26 @@ +@import '../../../styles/variables.less'; + + +:host { + position: relative; + display: block; + p.title { + max-width: 200px; + color: var(--scheme-color-0); + white-space: nowrap; + text-overflow: ellipsis; + position: relative; + display: inline-block; + overflow: hidden; + } + p.description { + max-width: 400px; + color: var(--scheme-color-2); + white-space: nowrap; + text-overflow: ellipsis; + position: relative; + display: inline-block; + overflow: hidden; + direction: rtl; + } +} \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/setup/origin/template.html b/application/client/src/app/ui/tabs/setup/origin/template.html new file mode 100644 index 000000000..55a8792ea --- /dev/null +++ b/application/client/src/app/ui/tabs/setup/origin/template.html @@ -0,0 +1,2 @@ +

{{title}}

+

{{description}}

diff --git a/application/client/src/app/ui/tabs/setup/provider.ts b/application/client/src/app/ui/tabs/setup/provider.ts new file mode 100644 index 000000000..ac16c2a10 --- /dev/null +++ b/application/client/src/app/ui/tabs/setup/provider.ts @@ -0,0 +1,153 @@ +import { SchemeProvider } from '@elements/scheme/provider'; +import { Field, FieldDesc, Value } from '@platform/types/bindings'; +import { SessionAction, LazyFieldDesc } from '@platform/types/bindings'; +import { components } from '@service/components'; +import { Logger } from '@env/logs'; +import { + LoadingCancelledEvent, + LoadingDoneEvent, + LoadingErrorEvent, + LoadingErrorsEvent, +} from '@platform/types/components'; +import { WrappedField } from '@ui/elements/scheme/field'; +import { SessionOrigin } from '@service/session/origin'; + +export class Proivder extends SchemeProvider { + protected pending: string[] = []; + protected readonly logger = new Logger(`ObserveSetupProivder`); + protected readonly values: Map = new Map(); + protected uuids: string[] = []; + protected fields: FieldDesc[] = []; + protected hasErrors: boolean = false; + + constructor(protected readonly origin: SessionOrigin, protected readonly target: string) { + super(target); + this.register( + components.subjects.get().LoadingDone.subscribe(this.onLoadingDone.bind(this)), + components.subjects.get().LoadingError.subscribe(this.onLoadingError.bind(this)), + components.subjects.get().LoadingErrors.subscribe(this.onLoadingErrors.bind(this)), + components.subjects + .get() + .LoadingCancelled.subscribe(this.onLoadingCancelled.bind(this)), + ); + } + + public isEmpty(): boolean { + return this.uuids.length === 0; + } + + public override load(): Promise { + this.pending.length === 0 && components.abort(this.pending); + this.pending = []; + return components + .getOptions(this.origin.getDef(), [this.target]) + .then((map: Map) => { + const fields = map.get(this.target); + if (!fields) { + return Promise.resolve(); + } + this.uuids = fields.map((field) => new WrappedField(field).id); + this.pending = fields + .map((field: FieldDesc) => { + const lazy = field as { Lazy: LazyFieldDesc }; + if (lazy.Lazy) { + return lazy.Lazy.id; + } else { + return undefined; + } + }) + .filter((id) => id !== undefined); + this.fields = fields; + return Promise.resolve(); + }); + } + + public override getFieldDescs(): FieldDesc[] { + return this.fields; + } + + public override setValue(uuid: string, value: Value): void { + if (!this.uuids.includes(uuid)) { + this.logger.error(`Field ${uuid} doesn't belong to current provider`); + return; + } + this.values.set(uuid, value); + const fields: Field[] = []; + this.values.forEach((value, id) => { + fields.push({ id, value }); + }); + components + .validate(this.origin.getDef(), this.target, fields) + .then((errs: Map) => { + this.checkErrorState(errs.size > 0); + // Always forward errors info, even no errors + this.subjects.get().error.emit(errs); + }) + .catch((err: Error) => { + this.logger.error(`Fail to validate settings: ${err.message}`); + }); + } + + public override destroy(): Promise { + this.unsubscribe(); + this.subjects.destroy(); + if (this.pending.length === 0) { + return Promise.resolve(); + } else { + return components.abort(this.pending).catch((err: Error) => { + this.logger.error(`Fail to abort loading of pending fields: ${err.message}`); + }); + } + } + + public override isValid(): boolean { + return !this.hasErrors; + } + + public override getFields(): Field[] { + const fields: Field[] = []; + this.values.forEach((value, id) => { + fields.push({ id, value }); + }); + return fields; + } + + protected checkErrorState(updated: boolean) { + if (updated === this.hasErrors) { + return; + } + this.hasErrors = updated; + } + + protected onLoadingDone(event: LoadingDoneEvent) { + event.fields.forEach((field) => { + if (!this.pending.includes(field.id)) { + return; + } + this.subjects.get().loaded.emit(field); + }); + } + protected onLoadingError(event: LoadingErrorEvent) { + const errors = new Map(); + event.fields.forEach((id) => { + if (!this.pending.includes(id)) { + return; + } + errors.set(id, event.error); + }); + errors.size > 0 && this.subjects.get().error.emit(errors); + this.checkErrorState(errors.size > 0); + } + protected onLoadingErrors(event: LoadingErrorsEvent) { + const errors = new Map(); + event.errors.forEach((error) => { + if (!this.pending.includes(error.id)) { + return; + } + errors.set(error.id, error.err); + }); + errors.size > 0 && this.subjects.get().error.emit(errors); + this.checkErrorState(errors.size > 0); + } + protected onLoadingCancelled(event: LoadingCancelledEvent) {} +} diff --git a/application/client/src/app/ui/tabs/setup/state.ts b/application/client/src/app/ui/tabs/setup/state.ts new file mode 100644 index 000000000..c046f9e29 --- /dev/null +++ b/application/client/src/app/ui/tabs/setup/state.ts @@ -0,0 +1,251 @@ +import { Ident, IODataType } from '@platform/types/bindings'; +import { components, isIOCompatible } from '@service/components'; +import { Logger } from '@env/logs'; +import { Subscriber, Subject, Subjects } from '@platform/env/subscription'; +import { Proivder } from './provider'; +import { SessionOrigin } from '@service/session/origin'; + +export interface IApi { + finish(origin: SessionOrigin): Promise; + cancel(): void; +} + +export interface ComponentDescription { + ident: Ident; + full: string; +} + +export class State extends Subscriber { + public sources: Ident[] = []; + public parsers: Ident[] = []; + public subjects: Subjects<{ + sources: Subject; + parsers: Subject; + error: Subject; + errorStateChange: Subject; + updated: Subject; + }> = new Subjects({ + sources: new Subject(), + parsers: new Subject(), + error: new Subject(), + errorStateChange: new Subject(), + updated: new Subject(), + }); + public selected: { + source: string | undefined; + parser: string | undefined; + } = { + source: undefined, + parser: undefined, + }; + public description: { + source: ComponentDescription | undefined; + parser: ComponentDescription | undefined; + } = { + source: undefined, + parser: undefined, + }; + public providers: { + source: Proivder | undefined; + parser: Proivder | undefined; + } = { + source: undefined, + parser: undefined, + }; + public preselected: { + parser: string | undefined; + source: string | undefined; + } = { + parser: undefined, + source: undefined, + }; + public locked: boolean = false; + + protected readonly logger = new Logger(`Setup`); + + constructor( + protected readonly origin: SessionOrigin, + parser: string | undefined, + source: string | undefined, + ) { + super(); + this.preselected.parser = parser; + this.preselected.source = source; + this.load(); + } + public destroy() { + this.unsubscribe(); + this.subjects.destroy(); + } + public setSourceOrigin(origin: SessionOrigin): Error | undefined { + if (!this.providers.parser || !this.providers.source) { + return new Error(`Source or parser isn't setup`); + } + if (this.locked) { + return new Error(`Not valid options`); + } + origin.options.setSource({ + uuid: this.providers.source.uuid, + fields: this.providers.source.getFields(), + }); + origin.options.setParser({ + uuid: this.providers.parser.uuid, + fields: this.providers.parser.getFields(), + }); + return undefined; + } + private async load() { + const [sources, parsers]: [Ident[] | void, Ident[] | void] = await Promise.all([ + components + .get(this.origin.getDef()) + .sources() + .catch((err: Error) => { + this.logger.error(`Fail to get sources list: ${err.message}`); + this.subjects.get().error.emit(err.message); + }), + components + .get(this.origin.getDef()) + .parsers() + .catch((err: Error) => { + this.logger.error(`Fail to get parsers list: ${err.message}`); + this.subjects.get().error.emit(err.message); + }), + ]); + if (!sources || !parsers) { + return; + } + this.sources = sources; + if ( + this.preselected.source && + this.sources.find((source) => source.uuid === this.preselected.source) + ) { + this.selected.source = this.preselected.source; + } else if (this.sources.length > 0) { + this.preselected.source = undefined; + this.selected.source = this.sources[0].uuid; + } + this.parsers = parsers; + if ( + this.preselected.parser && + this.parsers.find((parser) => parser.uuid === this.preselected.parser) + ) { + this.selected.parser = this.preselected.parser; + } else if (this.parsers.length > 0) { + this.preselected.parser = undefined; + this.selected.parser = this.parsers[0].uuid; + } + this.subjects.get().sources.emit(this.sources); + this.subjects.get().parsers.emit(this.parsers); + this.change().source(); + this.change().parser(); + } + + public change(): { + source(): void; + parser(): void; + } { + return { + source: (): void => { + if (this.selected.source === undefined) { + return; + } + if (this.providers.source !== undefined) { + this.providers.source.destroy().catch((err: Error) => { + this.logger.error(`Fail to destroy source provider: ${err.message}`); + }); + } + this.providers.source = undefined; + this.description.source = undefined; + const ident = this.sources.find((ident) => ident.uuid == this.selected.source); + if (ident !== undefined) { + this.description.source = { full: '', ident }; + } + const provider = new Proivder(this.origin, this.selected.source); + provider + .load() + .then(() => { + this.register( + provider.subjects.get().error.subscribe(() => { + this.checkErrors(); + this.subjects.get().errorStateChange.emit(); + }), + ); + this.providers.source = provider; + }) + .catch((err: Error) => { + this.logger.error(`Fail load source options desc: ${err.message}`); + }) + .finally(() => { + this.subjects.get().updated.emit(); + this.checkCompatibility(); + }); + }, + parser: (): void => { + if (this.selected.parser === undefined) { + return; + } + if (this.providers.parser !== undefined) { + this.providers.parser.destroy().catch((err: Error) => { + this.logger.error(`Fail to destroy parser provider: ${err.message}`); + }); + } + this.providers.parser = undefined; + this.description.parser = undefined; + const ident = this.parsers.find((ident) => ident.uuid == this.selected.parser); + if (ident !== undefined) { + this.description.parser = { full: '', ident }; + } + const provider = new Proivder(this.origin, this.selected.parser); + provider + .load() + .then(() => { + this.register( + provider.subjects.get().error.subscribe(() => { + this.checkErrors(); + this.subjects.get().errorStateChange.emit(); + }), + ); + this.providers.parser = provider; + }) + .catch((err: Error) => { + this.logger.error(`Fail load parser options desc: ${err.message}`); + }) + .finally(() => { + this.subjects.get().updated.emit(); + }); + }, + }; + } + + public isParserIOCompatible(uuid: string): boolean { + const source = this.sources.find((source) => source.uuid === this.selected.source); + const parser = this.parsers.find((parser) => parser.uuid === uuid); + if (!source || !parser) { + return false; + } + return isIOCompatible(source.io, parser.io); + } + + protected checkErrors() { + if (!this.providers.parser || !this.providers.source) { + this.locked = true; + } else { + this.locked = !this.providers.parser.isValid() || !this.providers.source.isValid(); + } + } + + protected checkCompatibility() { + const source = this.sources.find((source) => source.uuid === this.selected.source); + if (source === undefined || this.selected.parser === undefined) { + return; + } + const compatible = this.parsers + .filter((parser) => isIOCompatible(source.io, parser.io)) + .map((parser) => parser.uuid); + if (compatible.includes(this.selected.parser)) { + return; + } + this.selected.parser = compatible.length === 0 ? undefined : compatible[0]; + this.change().parser(); + } +} diff --git a/application/client/src/app/ui/tabs/observe/styles.less b/application/client/src/app/ui/tabs/setup/styles.less similarity index 93% rename from application/client/src/app/ui/tabs/observe/styles.less rename to application/client/src/app/ui/tabs/setup/styles.less index 7b24c33a1..2ea779544 100644 --- a/application/client/src/app/ui/tabs/observe/styles.less +++ b/application/client/src/app/ui/tabs/setup/styles.less @@ -23,7 +23,7 @@ right: 0; padding: 0 8px; background: var(--scheme-color-5); - box-shadow: 0 3px 3px rgba(0,0,0,0.4); + box-shadow: 0 3px 3px var(--scheme-color-7); height: 48px; overflow: hidden; & *.entity { @@ -71,4 +71,8 @@ margin: 0 8px; } } + p.desc { + color: var(--scheme-color-1); + font-size: 14px; + } } \ No newline at end of file diff --git a/application/client/src/app/ui/tabs/setup/template.html b/application/client/src/app/ui/tabs/setup/template.html new file mode 100644 index 000000000..e3e23d040 --- /dev/null +++ b/application/client/src/app/ui/tabs/setup/template.html @@ -0,0 +1,85 @@ +
+ +
+ + + + {{source.name}} + + + +
+
+

Used Parser:

+ + + + {{parser.name}} + + + +
+
+ + +
+
+
+
+
+ + {{state.description.source.ident.name}} + + + + + + {{state.description.parser.ident.name}} + + + + +
+
+ + {{state.description.source.ident.name}} +

{{state.description.source.ident.desc}}

+
+ + {{state.description.parser.ident.name}} +

{{state.description.parser.ident.desc}}

+
+
+
+
diff --git a/application/client/src/app/ui/views/sidebar/observe/common/module.ts b/application/client/src/app/ui/views/sidebar/observe/common/module.ts deleted file mode 100644 index a2f9f3f45..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/common/module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { Title } from './title/component'; - -@NgModule({ - imports: [CommonModule], - declarations: [Title], - exports: [Title], -}) -export class CommonObserveModule {} diff --git a/application/client/src/app/ui/views/sidebar/observe/common/title/component.ts b/application/client/src/app/ui/views/sidebar/observe/common/title/component.ts deleted file mode 100644 index ec30184e2..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/common/title/component.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Component, Input, Output, EventEmitter } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; - -export interface IButton { - icon: string; - handler: () => void; -} - -@Component({ - selector: 'app-views-observed-list-title', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class Title { - @Input() title!: string; - @Input() subtitle: string | undefined; - @Input() buttons: IButton[] = []; - @Input() opened!: boolean; - @Input() hideToggle: boolean | undefined; - @Output() toggled: EventEmitter = new EventEmitter(); - - public click(button: IButton) { - button.handler(); - } - - public toggle() { - this.opened = !this.opened; - this.toggled.emit(this.opened); - } -} -export interface Title extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/common/title/styles.less b/application/client/src/app/ui/views/sidebar/observe/common/title/styles.less deleted file mode 100644 index fa1135c62..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/common/title/styles.less +++ /dev/null @@ -1,23 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - position: relative; - display: flex; - padding: 0 24px; - margin: 0; - text-align: left; - align-items: center; - white-space: nowrap; - font-size: 0.9rem; - font-weight: 400; - span.title{ - color: var(--scheme-color-2); - } - span.subtitle{ - padding-left: 6px; - color: var(--scheme-color-3); - } - & span.filler { - flex: auto; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/common/title/template.html b/application/client/src/app/ui/views/sidebar/observe/common/title/template.html deleted file mode 100644 index 62d2371e3..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/common/title/template.html +++ /dev/null @@ -1,5 +0,0 @@ -{{title}} -({{subtitle}}) - - - diff --git a/application/client/src/app/ui/views/sidebar/observe/component.ts b/application/client/src/app/ui/views/sidebar/observe/component.ts index b4b874d87..b4bc9f3e6 100644 --- a/application/client/src/app/ui/views/sidebar/observe/component.ts +++ b/application/client/src/app/ui/views/sidebar/observe/component.ts @@ -1,5 +1,13 @@ -import { Component, Input, ChangeDetectorRef, ViewEncapsulation } from '@angular/core'; +import { + Component, + Input, + ChangeDetectorRef, + ViewEncapsulation, + AfterContentInit, +} from '@angular/core'; import { Session } from '@service/session'; +import { Stream } from '@service/session/dependencies/stream'; +import { ObserveOperation } from '@service/session/dependencies/observing/operation'; import { Ilc, IlcInterface } from '@env/decorators/component'; import { Initial } from '@env/decorators/initial'; import { ChangesDetector } from '@ui/env/extentions/changes'; @@ -13,11 +21,41 @@ import { ChangesDetector } from '@ui/env/extentions/changes'; }) @Initial() @Ilc() -export class Observed extends ChangesDetector { +export class Observed extends ChangesDetector implements AfterContentInit { @Input() session!: Session; + public stream!: Stream; + public operations: ObserveOperation[] = []; + public active: ObserveOperation[] = []; + public inactive: ObserveOperation[] = []; + constructor(cdRef: ChangeDetectorRef) { super(cdRef); } + + public ngAfterContentInit(): void { + this.stream = this.session.stream; + this.update(); + this.env().subscriber.register( + this.stream.subjects.get().sources.subscribe(() => { + this.update(); + this.detectChanges(); + }), + this.stream.subjects.get().finished.subscribe(() => { + this.update(); + this.detectChanges(); + }), + this.stream.sde.subjects.get().selected.subscribe(() => { + this.update(); + this.detectChanges(); + }), + ); + } + + protected update() { + this.operations = this.stream.observe().operations(); + this.active = this.operations.filter((operation) => operation.isRunning()); + this.inactive = this.operations.filter((operation) => !operation.isRunning()); + } } export interface Observed extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/component.ts b/application/client/src/app/ui/views/sidebar/observe/element/component.ts deleted file mode 100644 index 16207193d..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Component, Input, HostListener } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Element } from './element'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-views-observed-list-item', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class Item { - public readonly Context = $.Origin.Context; - public readonly Source = $.Origin.Stream.Stream.Source; - - @Input() element!: Element; - - @HostListener('click') onClick() { - this.element.select(); - } - - @HostListener('contextmenu', ['$event']) async onContextMenu(event: MouseEvent) { - const items = this.element.provider.contextMenu(this.element.source); - if (items.length > 0) { - items.push({}); - } - items.push({ - caption: 'Reopen in New Tab', - handler: () => { - this.element.provider.openAsNew(this.element.source).catch((err: Error) => { - this.log().error(`Fail to open Source: ${err.message}`); - }); - }, - }); - this.ilc().emitter.ui.contextmenu.open({ - items, - x: event.x, - y: event.y, - }); - } -} -export interface Item extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/element.ts b/application/client/src/app/ui/views/sidebar/observe/element/element.ts deleted file mode 100644 index bb840c3e2..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/element.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ObserveSource } from '@service/session/dependencies/observing/source'; -import { File } from '@platform/types/files'; -import { Mutable } from '@platform/types/unity/mutable'; -import { Provider } from '@service/session/dependencies/observing/provider'; - -import * as $ from '@platform/types/observe'; - -export class Element { - public readonly source: ObserveSource; - public readonly provider: Provider; - public readonly id: number | undefined; - public readonly file: File | undefined; - public selected: boolean = false; - - constructor(source: ObserveSource, provider: Provider) { - this.source = source; - this.provider = provider; - const session = this.provider.session; - const sourceId = this.source.observe.origin.source(); - this.id = - sourceId !== undefined ? session.stream.observe().descriptions.id(sourceId) : undefined; - this.selected = session.stream.sde.selecting().is(this.source.observe.uuid); - } - - public select(): void { - const sde = this.provider.session.stream.sde; - this.selected = sde.selecting().select(this.source.observe.uuid); - } - - public set(): { file(file: File): Element } { - return { - file: (file: File): Element => { - (this as Mutable).file = file; - return this; - }, - }; - } - - public nature(): $.Origin.Context | $.Origin.Stream.Stream.Source { - return this.source.observe.origin.getNatureAlias(); - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/file/component.ts b/application/client/src/app/ui/views/sidebar/observe/element/file/component.ts deleted file mode 100644 index 9866b90ae..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/file/component.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Component, Input, ChangeDetectorRef, ElementRef, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { File } from '@platform/types/files'; -import { Element } from '../element'; -import { Mutable } from '@platform/types/unity/mutable'; -import { stop } from '@ui/env/dom'; - -@Component({ - selector: 'app-views-observed-file', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class Item extends ChangesDetector implements AfterContentInit { - @Input() element!: Element; - - public readonly file: File | undefined; - - constructor(cdRef: ChangeDetectorRef, private _self: ElementRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - if (this.element.file === undefined) { - this.log().error(`Field "file" in Element is undefined`); - return; - } - (this as Mutable).file = this.element.file; - } - - public isActive(): boolean { - return this.element.source.observer !== undefined; - } - - public openAsNew(event: MouseEvent): void { - stop(event); - this.element.provider.openAsNew(this.element.source).catch((err: Error) => { - this.log().error(`Fail to restart file: ${err.message}`); - }); - } - - public stop(event: MouseEvent): void { - stop(event); - const observer = this.element.source.observer; - if (observer === undefined) { - return; - } - observer.abort().catch((err: Error) => { - this.log().error(`Fail to abort file tailing: ${err.message}`); - }); - } -} -export interface Item extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/file/styles.less b/application/client/src/app/ui/views/sidebar/observe/element/file/styles.less deleted file mode 100644 index fa223d7b3..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/file/styles.less +++ /dev/null @@ -1,51 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - position: relative; - display: flex; - padding: 0; - margin: 0; - flex-direction: row; - align-items: center; - width: 100%; - height: 32px; - overflow: hidden; - & span.ext { - display: block; - position: relative; - font-size: 14px; - height: 18px; - line-height: 14px; - font-weight: 700; - margin-right: 12px; - margin-left: 6px; - color: var(--scheme-color-3); - &.active { - color: var(--scheme-color-accent); - } - } - & div.info { - overflow: hidden; - flex: auto; - text-align: left; - & span { - display: block; - position: relative; - overflow: hidden; - text-overflow: ellipsis; - direction: rtl; - line-height: 0.9rem; - white-space: nowrap; - } - & span.name { - color: var(--scheme-color-1); - margin-bottom: 3px; - } - & span.path { - color: var(--scheme-color-3); - } - } - & div.controlls { - margin: 0 24px 0 12px; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/file/template.html b/application/client/src/app/ui/views/sidebar/observe/element/file/template.html deleted file mode 100644 index c45d03c0c..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/file/template.html +++ /dev/null @@ -1,14 +0,0 @@ - - No data about file - - - {{file.ext}} -
- {{file.name}} - {{file.path}} -
-
- - -
-
diff --git a/application/client/src/app/ui/views/sidebar/observe/element/module.ts b/application/client/src/app/ui/views/sidebar/observe/element/module.ts deleted file mode 100644 index 9741b26fc..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { Item } from './component'; -import { Item as FileItem } from './file/component'; -import { Item as ProcessItem } from './process/component'; -import { Item as SerialItem } from './serial/component'; -import { Item as TCPItem } from './tcp/component'; -import { Item as UDPItem } from './udp/component'; -import { Signature } from './signature/component'; - -@NgModule({ - imports: [CommonModule], - declarations: [Item, FileItem, ProcessItem, SerialItem, TCPItem, UDPItem, Signature], - exports: [Item], -}) -export class ElementModule {} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/process/component.ts b/application/client/src/app/ui/views/sidebar/observe/element/process/component.ts deleted file mode 100644 index 1402a070f..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/process/component.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Component, Input, ChangeDetectorRef, ElementRef, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { Element } from '../element'; -import { Mutable } from '@platform/types/unity/mutable'; -import { stop } from '@ui/env/dom'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-views-observed-process', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class Item extends ChangesDetector implements AfterContentInit { - @Input() element!: Element; - - public readonly connection: $.Origin.Stream.Stream.Process.IConfiguration | undefined; - public readonly selected!: boolean; - - constructor(cdRef: ChangeDetectorRef, private _self: ElementRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - const conf = - this.element.source.observe.origin.as<$.Origin.Stream.Stream.Process.Configuration>( - $.Origin.Stream.Stream.Process.Configuration, - ); - if (conf === undefined) { - this.log().error(`Expected origin Source would be Process`); - return; - } - (this as Mutable).connection = conf.configuration; - } - - public isActive(): boolean { - return this.element.source.observer !== undefined; - } - - public restart(event: MouseEvent): void { - stop(event); - this.element.provider.clone(this.element.source.observe).catch((err: Error) => { - this.log().error(`Fail to restart process: ${err.message}`); - }); - } - - public stop(event: MouseEvent): void { - stop(event); - const observer = this.element.source.observer; - if (observer === undefined) { - return; - } - observer.abort().catch((err: Error) => { - this.log().error(`Fail to abort process: ${err.message}`); - }); - } -} -export interface Item extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/process/styles.less b/application/client/src/app/ui/views/sidebar/observe/element/process/styles.less deleted file mode 100644 index 404ed07c5..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/process/styles.less +++ /dev/null @@ -1,51 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - position: relative; - display: flex; - padding: 0; - margin: 0; - flex-direction: row; - align-items: center; - width: 100%; - height: 32px; - overflow: hidden; - & span.ext { - display: block; - position: relative; - margin-right: 12px; - margin-left: 6px; - line-height: 10px; - & span.codicon { - color: var(--scheme-color-3); - font-weight: 700; - &.active { - color: var(--scheme-color-accent); - } - } - } - & div.info { - overflow: hidden; - flex: auto; - text-align: left; - & span { - display: block; - position: relative; - overflow: hidden; - text-overflow: ellipsis; - direction: rtl; - line-height: 0.9rem; - white-space: nowrap; - } - & span.name { - color: var(--scheme-color-1); - margin-bottom: 3px; - } - & span.path { - color: var(--scheme-color-3); - } - } - & div.controlls { - margin: 0 24px 0 12px; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/process/template.html b/application/client/src/app/ui/views/sidebar/observe/element/process/template.html deleted file mode 100644 index 5b9f49cce..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/process/template.html +++ /dev/null @@ -1,16 +0,0 @@ - - No data about process - - - - - -
- {{connection.command}} - {{connection.cwd}} -
-
- - -
-
diff --git a/application/client/src/app/ui/views/sidebar/observe/element/serial/component.ts b/application/client/src/app/ui/views/sidebar/observe/element/serial/component.ts deleted file mode 100644 index 36caabd1d..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/serial/component.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Component, Input, ChangeDetectorRef, ElementRef, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { Element } from '../element'; -import { Mutable } from '@platform/types/unity/mutable'; -import { stop } from '@ui/env/dom'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-views-observed-serial', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class Item extends ChangesDetector implements AfterContentInit { - @Input() element!: Element; - - public readonly connection: $.Origin.Stream.Stream.Serial.IConfiguration | undefined; - - constructor(cdRef: ChangeDetectorRef, private _self: ElementRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - const conf = - this.element.source.observe.origin.as<$.Origin.Stream.Stream.Serial.Configuration>( - $.Origin.Stream.Stream.Serial.Configuration, - ); - if (conf === undefined) { - this.log().error(`Expected origin Source would be TCP`); - return; - } - (this as Mutable).connection = conf.configuration; - } - - public isActive(): boolean { - return this.element.source.observer !== undefined; - } - - public restart(event: MouseEvent): void { - stop(event); - this.element.provider.clone(this.element.source.observe).catch((err: Error) => { - this.log().error(`Fail to restart Serial connection: ${err.message}`); - }); - } - - public stop(event: MouseEvent): void { - stop(event); - const observer = this.element.source.observer; - if (observer === undefined) { - return; - } - observer.abort().catch((err: Error) => { - this.log().error(`Fail to abort Serial connection: ${err.message}`); - }); - } -} -export interface Item extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/serial/styles.less b/application/client/src/app/ui/views/sidebar/observe/element/serial/styles.less deleted file mode 100644 index 404ed07c5..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/serial/styles.less +++ /dev/null @@ -1,51 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - position: relative; - display: flex; - padding: 0; - margin: 0; - flex-direction: row; - align-items: center; - width: 100%; - height: 32px; - overflow: hidden; - & span.ext { - display: block; - position: relative; - margin-right: 12px; - margin-left: 6px; - line-height: 10px; - & span.codicon { - color: var(--scheme-color-3); - font-weight: 700; - &.active { - color: var(--scheme-color-accent); - } - } - } - & div.info { - overflow: hidden; - flex: auto; - text-align: left; - & span { - display: block; - position: relative; - overflow: hidden; - text-overflow: ellipsis; - direction: rtl; - line-height: 0.9rem; - white-space: nowrap; - } - & span.name { - color: var(--scheme-color-1); - margin-bottom: 3px; - } - & span.path { - color: var(--scheme-color-3); - } - } - & div.controlls { - margin: 0 24px 0 12px; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/serial/template.html b/application/client/src/app/ui/views/sidebar/observe/element/serial/template.html deleted file mode 100644 index 276c1b3d0..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/serial/template.html +++ /dev/null @@ -1,16 +0,0 @@ - - No data about serial connection - - - - - -
- {{connection.path}} - boud rate: {{connection.baud_rate.toString()}} -
-
- - -
-
diff --git a/application/client/src/app/ui/views/sidebar/observe/element/signature/component.ts b/application/client/src/app/ui/views/sidebar/observe/element/signature/component.ts deleted file mode 100644 index 5ba82829e..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/signature/component.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Component, Input, ChangeDetectorRef, ElementRef, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { getSourceColor } from '@ui/styles/colors'; -import { Element } from '../element'; - -@Component({ - selector: 'app-views-observed-list-item-signature', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class Signature extends ChangesDetector implements AfterContentInit { - @Input() element!: Element; - @Input() id!: number | undefined; - - public selected!: boolean; - - constructor(cdRef: ChangeDetectorRef, private _self: ElementRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - this.selected = this.element.selected; - } - - public getSourceMarkerStyles(): { [key: string]: string } { - return this.id === undefined - ? {} - : { - background: getSourceColor(this.id), - }; - } -} -export interface List extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/signature/styles.less b/application/client/src/app/ui/views/sidebar/observe/element/signature/styles.less deleted file mode 100644 index d377d303d..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/signature/styles.less +++ /dev/null @@ -1,26 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - position: relative; - display: block; - padding: 0; - margin: 0 6px 0 0 ; - height: 32px; - & span.color { - position: relative; - display: inline-block; - width: 4px; - height: 100%; - } - & span.selected { - position: absolute; - display: block; - width: 8px; - height: 8px; - border-radius: 4px; - top: 50%; - margin-top: -4px; - left: -17px; - background-color: var(--scheme-color-accent); - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/signature/template.html b/application/client/src/app/ui/views/sidebar/observe/element/signature/template.html deleted file mode 100644 index 99e32df21..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/signature/template.html +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/application/client/src/app/ui/views/sidebar/observe/element/styles.less b/application/client/src/app/ui/views/sidebar/observe/element/styles.less deleted file mode 100644 index 77d20f99e..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/styles.less +++ /dev/null @@ -1,18 +0,0 @@ -@import '../../../../styles/variables.less'; - -:host { - position: relative; - display: flex; - padding: 0; - margin: 0; - flex-direction: row; - align-items: center; - width: ~"calc(100% - 24px)"; - height: 32px; - overflow: hidden; - padding: 3px 24px; - cursor: default; - &:hover { - background: var(--scheme-color-5); - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/tcp/component.ts b/application/client/src/app/ui/views/sidebar/observe/element/tcp/component.ts deleted file mode 100644 index 5c9d95105..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/tcp/component.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Component, Input, ChangeDetectorRef, ElementRef, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { Element } from '../element'; -import { Mutable } from '@platform/types/unity/mutable'; -import { stop } from '@ui/env/dom'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-views-observed-tcp', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class Item extends ChangesDetector implements AfterContentInit { - @Input() element!: Element; - - public readonly connection: $.Origin.Stream.Stream.TCP.IConfiguration | undefined; - - constructor(cdRef: ChangeDetectorRef, private _self: ElementRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - const conf = - this.element.source.observe.origin.as<$.Origin.Stream.Stream.TCP.Configuration>( - $.Origin.Stream.Stream.TCP.Configuration, - ); - if (conf === undefined) { - this.log().error(`Expected origin Source would be TCP`); - return; - } - (this as Mutable).connection = conf.configuration; - } - - public isActive(): boolean { - return this.element.source.observer !== undefined; - } - - public restart(event: MouseEvent): void { - stop(event); - this.element.provider.clone(this.element.source.observe).catch((err: Error) => { - this.log().error(`Fail to restart TCP connection: ${err.message}`); - }); - } - - public stop(event: MouseEvent): void { - stop(event); - const observer = this.element.source.observer; - if (observer === undefined) { - return; - } - observer.abort().catch((err: Error) => { - this.log().error(`Fail to abort TCP connection: ${err.message}`); - }); - } -} -export interface Item extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/tcp/styles.less b/application/client/src/app/ui/views/sidebar/observe/element/tcp/styles.less deleted file mode 100644 index fa223d7b3..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/tcp/styles.less +++ /dev/null @@ -1,51 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - position: relative; - display: flex; - padding: 0; - margin: 0; - flex-direction: row; - align-items: center; - width: 100%; - height: 32px; - overflow: hidden; - & span.ext { - display: block; - position: relative; - font-size: 14px; - height: 18px; - line-height: 14px; - font-weight: 700; - margin-right: 12px; - margin-left: 6px; - color: var(--scheme-color-3); - &.active { - color: var(--scheme-color-accent); - } - } - & div.info { - overflow: hidden; - flex: auto; - text-align: left; - & span { - display: block; - position: relative; - overflow: hidden; - text-overflow: ellipsis; - direction: rtl; - line-height: 0.9rem; - white-space: nowrap; - } - & span.name { - color: var(--scheme-color-1); - margin-bottom: 3px; - } - & span.path { - color: var(--scheme-color-3); - } - } - & div.controlls { - margin: 0 24px 0 12px; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/tcp/template.html b/application/client/src/app/ui/views/sidebar/observe/element/tcp/template.html deleted file mode 100644 index 64f1441f9..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/tcp/template.html +++ /dev/null @@ -1,13 +0,0 @@ - - No data about TCP connection - - - TCP -
- {{connection.bind_addr}} -
-
- - -
-
diff --git a/application/client/src/app/ui/views/sidebar/observe/element/template.html b/application/client/src/app/ui/views/sidebar/observe/element/template.html deleted file mode 100644 index d12c7bdce..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/template.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/application/client/src/app/ui/views/sidebar/observe/element/udp/component.ts b/application/client/src/app/ui/views/sidebar/observe/element/udp/component.ts deleted file mode 100644 index 15a56bc04..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/udp/component.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Component, Input, ChangeDetectorRef, ElementRef, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { ChangesDetector } from '@ui/env/extentions/changes'; -import { Element } from '../element'; -import { Mutable } from '@platform/types/unity/mutable'; -import { stop } from '@ui/env/dom'; - -import * as $ from '@platform/types/observe'; - -@Component({ - selector: 'app-views-observed-udp', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class Item extends ChangesDetector implements AfterContentInit { - @Input() element!: Element; - - public readonly connection: $.Origin.Stream.Stream.UDP.IConfiguration | undefined; - - constructor(cdRef: ChangeDetectorRef, private _self: ElementRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - const conf = - this.element.source.observe.origin.as<$.Origin.Stream.Stream.UDP.Configuration>( - $.Origin.Stream.Stream.UDP.Configuration, - ); - if (conf === undefined) { - this.log().error(`Expected origin Source would be UDP`); - return; - } - (this as Mutable).connection = conf.configuration; - } - - public isActive(): boolean { - return this.element.source.observer !== undefined; - } - - public restart(event: MouseEvent): void { - stop(event); - this.element.provider.clone(this.element.source.observe).catch((err: Error) => { - this.log().error(`Fail to restart UDP connection: ${err.message}`); - }); - } - - public stop(event: MouseEvent): void { - stop(event); - const observer = this.element.source.observer; - if (observer === undefined) { - return; - } - observer.abort().catch((err: Error) => { - this.log().error(`Fail to abort UDP connection: ${err.message}`); - }); - } -} -export interface Item extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/udp/styles.less b/application/client/src/app/ui/views/sidebar/observe/element/udp/styles.less deleted file mode 100644 index ab053647a..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/udp/styles.less +++ /dev/null @@ -1,51 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - position: relative; - display: flex; - padding: 0; - margin: 0; - flex-direction: row; - align-items: center; - width: 100%; - height: 32px; - overflow: hidden; - & span.ext { - display: block; - position: relative; - margin-left: 6px; - font-size: 14px; - height: 18px; - line-height: 14px; - font-weight: 700; - margin-right: 12px; - color: var(--scheme-color-3); - &.active { - color: var(--scheme-color-accent); - } - } - & div.info { - overflow: hidden; - flex: auto; - text-align: left; - & span { - display: block; - position: relative; - overflow: hidden; - text-overflow: ellipsis; - direction: rtl; - line-height: 0.9rem; - white-space: nowrap; - } - & span.name { - color: var(--scheme-color-1); - margin-bottom: 3px; - } - & span.path { - color: var(--scheme-color-3); - } - } - & div.controlls { - margin: 0 24px 0 12px; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/element/udp/template.html b/application/client/src/app/ui/views/sidebar/observe/element/udp/template.html deleted file mode 100644 index ace0ad545..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/element/udp/template.html +++ /dev/null @@ -1,14 +0,0 @@ - - No data about UDP connection - - - UDP -
- {{connection.bind_addr}} - {{connection.multicast[0].multiaddr}} -
-
- - -
-
diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/component.ts b/application/client/src/app/ui/views/sidebar/observe/lists/component.ts deleted file mode 100644 index c85f754be..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/component.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { - Component, - Input, - OnDestroy, - Inject, - AfterContentInit, - ChangeDetectorRef, -} from '@angular/core'; -import { Provider as ProviderBase } from '@service/session/dependencies/observing/provider'; -import { Mutable } from '@platform/types/unity/mutable'; -import { Base } from '../states/state'; -import { ChangesDetector } from '@ui/env/extentions/changes'; - -@Component({ - selector: 'app-observe-list-base', - template: '', - standalone: false, -}) -export class ListBase - extends ChangesDetector - implements OnDestroy, AfterContentInit -{ - public readonly state!: State; - - @Input() public provider!: Provider; - - constructor(@Inject('defaults') protected readonly defaults: State, cdRef: ChangeDetectorRef) { - super(cdRef); - } - - public ngAfterContentInit(): void { - const state = this.provider.session.storage.get(this.defaults.key()); - (this as Mutable>).state = - state === undefined ? this.defaults : state; - } - - public ngOnDestroy(): void { - this.provider.session.storage.set(this.defaults.key(), this.state); - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/file/component.ts b/application/client/src/app/ui/views/sidebar/observe/lists/file/component.ts deleted file mode 100644 index a5ee4b397..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/file/component.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { Component, ChangeDetectorRef, AfterContentInit } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Element } from '../../element/element'; -import { File } from '@platform/types/files'; -import { State } from '../../states/files'; -import { IButton } from '../../common/title/component'; -import { ListBase } from '../component'; -import { Provider } from '@service/session/dependencies/observing/implementations/files'; -import { ObserveSource } from '@service/session/dependencies/observing/source'; - -import * as $ from '@platform/types/observe'; -import * as Factory from '@platform/types/observe/factory'; - -@Component({ - selector: 'app-views-observed-list-file', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class List extends ListBase implements AfterContentInit { - public tailing: Element[] = []; - public offline: Element[] = []; - public buttons: IButton[] = [ - { - icon: 'codicon-tasklist', - handler: () => { - this.provider.recent(); - }, - }, - ]; - public warning: string | undefined = undefined; - - protected inited: boolean = false; - - constructor(cdRef: ChangeDetectorRef) { - super(new State(), cdRef); - } - - public override ngAfterContentInit(): void { - super.ngAfterContentInit(); - this.update(); - this.env().subscriber.register( - this.provider.subjects.get().updated.subscribe(() => { - this.update().detectChanges(); - }), - ); - } - - protected update(): List { - const filterFileNature = (s: ObserveSource): boolean => { - return s.observe.origin.nature().alias() === $.Origin.Context.File; - }; - const filterAsFile = (s: ObserveSource): $.Origin.File.Configuration => { - return s.observe.origin.as<$.Origin.File.Configuration>( - $.Origin.File.Configuration, - ) as $.Origin.File.Configuration; - }; - const asFileInstance = (s: ObserveSource): $.Origin.File.Configuration => { - return s.observe.origin.as<$.Origin.File.Configuration>( - $.Origin.File.Configuration, - ) as $.Origin.File.Configuration; - }; - const tailing = this.provider - .sources() - .filter((s) => s.observer !== undefined) - .filter(filterFileNature); - const offline = this.provider - .sources() - .filter((s) => s.observer === undefined) - .filter(filterFileNature); - const attachNewSourceErr = this.provider.getNewSourceError(); - this.warning = attachNewSourceErr instanceof Error ? attachNewSourceErr.message : undefined; - this.ilc() - .services.system.bridge.files() - .getByPathWithCache([ - ...tailing.map(filterAsFile).map((i) => i.filename()), - ...offline.map(filterAsFile).map((i) => i.filename()), - ]) - .then((files: File[]) => { - this.tailing = tailing - .map((s) => { - const filename = asFileInstance(s).filename(); - const file = files.find((f) => f.filename === filename); - if (file === undefined) { - this.log().error( - `Fail to find a file ${filename} in cache; or get file metadata`, - ); - } - return file === undefined - ? null - : new Element(s, this.provider).set().file(file); - }) - .filter((i) => i !== null) as Element[]; - this.offline = offline - .map((s) => { - const filename = asFileInstance(s).filename(); - const file = files.find((f) => f.filename === filename); - if (file === undefined) { - this.log().error( - `Fail to find a file ${filename} in cache; or get file metadata`, - ); - } - return file === undefined - ? null - : new Element(s, this.provider).set().file(file); - }) - .filter((i) => i !== null) as Element[]; - }) - .catch((err: Error) => { - this.log().error(`Fail load stat of files: ${err.message}`); - }) - .finally(() => { - this.detectChanges(); - }); - return this; - } - - public toggled(opened: boolean) { - this.state.toggleQuickSetup(opened); - } - - public attach() { - const last = this.provider.last(); - if (last === undefined) { - return; - } - const lastFile = last.origin.as<$.Origin.File.Configuration>($.Origin.File.Configuration); - if (lastFile === undefined) { - return; - } - this.ilc() - .services.system.bridge.files() - .select.any() - .then((files: File[]) => { - if (files.length === 0) { - return; - } - this.provider.session.stream - .observe() - .start( - files.length === 1 - ? new Factory.File() - .type(lastFile.configuration[1]) - .file(files[0].filename) - .protocol(last.parser.instance.alias()) - .get() - : new Factory.Concat() - .type(lastFile.configuration[1]) - .files(files.map((f) => f.filename)) - .protocol(last.parser.instance.alias()) - .get(), - ) - .catch((err: Error) => { - this.log().error(`Fail to observe: ${err.message}`); - }); - }) - .catch((err: Error) => { - this.log().error(`Fail to select file(s): ${err.message}`); - }); - } -} -export interface List extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/file/module.ts b/application/client/src/app/ui/views/sidebar/observe/lists/file/module.ts deleted file mode 100644 index 0965ac708..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/file/module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { List } from './component'; -import { ElementModule } from '../../element/module'; -import { CommonObserveModule } from '../../common/module'; -import { MatButtonModule } from '@angular/material/button'; - -@NgModule({ - imports: [CommonModule, ElementModule, CommonObserveModule, MatButtonModule], - declarations: [List], - exports: [List], -}) -export class ListModule {} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/file/styles.less b/application/client/src/app/ui/views/sidebar/observe/lists/file/styles.less deleted file mode 100644 index b4d727cfc..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/file/styles.less +++ /dev/null @@ -1,23 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - position: relative; - display: block; - padding: 0; - margin: 0; - p.subtitle{ - color: var(--scheme-color-3); - text-align: right; - } - div.quicksetup { - padding: 0 24px 24px 24px; - } - div.controlls{ - text-align: right; - } - & button { - position: relative; - display: inline-block; - margin: 6px 0 6px 6px; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/file/template.html b/application/client/src/app/ui/views/sidebar/observe/lists/file/template.html deleted file mode 100644 index 898c3a222..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/file/template.html +++ /dev/null @@ -1,15 +0,0 @@ - -
-

{{warning}}

-
- -
-
-
- - -
-
- - -
\ No newline at end of file diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/process/component.ts b/application/client/src/app/ui/views/sidebar/observe/lists/process/component.ts deleted file mode 100644 index 8bcdcc303..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/process/component.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Component, ChangeDetectorRef, AfterContentInit, ViewChild } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Element } from '../../element/element'; -import { Action } from '@ui/tabs/observe/action'; -import { QuickSetup } from '@tabs/observe/origin/stream/transport/setup/quick/process/component'; -import { IButton } from '../../common/title/component'; -import { State } from '../../states/process'; -import { ListBase } from '../component'; -import { Provider } from '@service/session/dependencies/observing/implementations/processes'; -import { Configuration } from '@platform/types/observe/origin/stream/process'; -import { notifications, Notification } from '@ui/service/notifications'; - -import * as Factroy from '@platform/types/observe/factory'; - -@Component({ - selector: 'app-views-observed-list-process', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class List extends ListBase implements AfterContentInit { - @ViewChild('quicksetupref') public quickSetupRef!: QuickSetup; - - public tailing: Element[] = []; - public offline: Element[] = []; - public action: Action = new Action(); - public initial: Configuration = new Configuration(Configuration.initial(), undefined); - public buttons: IButton[] = [ - { - icon: 'codicon-tasklist', - handler: () => { - this.provider.recent(); - }, - }, - { - icon: 'codicon-empty-window', - handler: () => { - this.provider.openNewSessionOptions(); - }, - }, - ]; - - constructor(cdRef: ChangeDetectorRef) { - super(new State(), cdRef); - } - - public override ngAfterContentInit(): void { - super.ngAfterContentInit(); - this.update(); - this.env().subscriber.register( - this.provider.subjects.get().updated.subscribe(() => { - this.update().detectChanges(); - }), - ); - this.env().subscriber.register( - this.action.subjects.get().apply.subscribe(() => { - this.provider - .openAsNewOrigin( - new Factroy.Stream().asText().process(this.initial.configuration).get(), - ) - .then(() => { - const cloned = this.initial.sterilized(); - this.action.applied(); - this.initial.overwrite({ - command: '', - cwd: cloned.cwd, - envs: cloned.envs, - }); - }) - .catch((err: Error) => { - notifications.notify( - new Notification({ - message: err.message, - actions: [], - }), - ); - this.log().error(`Fail to apply connection to Process: ${err.message}`); - }); - }), - ); - } - - public toggled(opened: boolean) { - this.state.toggleQuickSetup(opened); - } - - protected update(): List { - this.tailing = this.provider - .sources() - .filter((s) => s.observer !== undefined) - .map((s) => new Element(s, this.provider)); - this.offline = this.provider - .sources() - .filter((s) => s.observer === undefined) - .map((s) => new Element(s, this.provider)); - return this; - } -} -export interface List extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/process/module.ts b/application/client/src/app/ui/views/sidebar/observe/lists/process/module.ts deleted file mode 100644 index f4020c2c4..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/process/module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { List } from './component'; -import { ElementModule } from '../../element/module'; -import { CommonObserveModule } from '../../common/module'; -import { QuickSetupModule } from '@ui/tabs/observe/origin/stream/transport/setup/quick/process/module'; - -@NgModule({ - imports: [CommonModule, ElementModule, CommonObserveModule, QuickSetupModule], - declarations: [List], - exports: [List], -}) -export class ListModule {} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/process/styles.less b/application/client/src/app/ui/views/sidebar/observe/lists/process/styles.less deleted file mode 100644 index c1c9e9f85..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/process/styles.less +++ /dev/null @@ -1,15 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - position: relative; - display: block; - padding: 0; - margin: 0; - p.subtitle{ - color: var(--scheme-color-3); - text-align: right; - } - div.quicksetup { - padding: 0 24px 24px 24px; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/process/template.html b/application/client/src/app/ui/views/sidebar/observe/lists/process/template.html deleted file mode 100644 index d5ba862cc..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/process/template.html +++ /dev/null @@ -1,12 +0,0 @@ - -
- -
-
- - -
-
- - -
\ No newline at end of file diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/serial/component.ts b/application/client/src/app/ui/views/sidebar/observe/lists/serial/component.ts deleted file mode 100644 index 13b8af7d3..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/serial/component.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Component, ChangeDetectorRef, AfterContentInit, ViewChild } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Provider } from '@service/session/dependencies/observing/implementations/serial'; -import { Element } from '../../element/element'; -import { Action } from '@ui/tabs/observe/action'; -import { QuickSetup } from '@tabs/observe/origin/stream/transport/setup/quick/serial/component'; -import { IButton } from '../../common/title/component'; -import { State } from '../../states/serial'; -import { ListBase } from '../component'; -import { Configuration } from '@platform/types/observe/origin/stream/serial'; -import { notifications, Notification } from '@ui/service/notifications'; - -import * as Factroy from '@platform/types/observe/factory'; - -@Component({ - selector: 'app-views-observed-list-serial', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class List extends ListBase implements AfterContentInit { - @ViewChild('quicksetupref') public quickSetupRef!: QuickSetup; - - public tailing: Element[] = []; - public offline: Element[] = []; - public action: Action = new Action(); - public initial: Configuration = new Configuration(Configuration.initial(), undefined); - public buttons: IButton[] = [ - { - icon: 'codicon-tasklist', - handler: () => { - this.provider.recent(); - }, - }, - { - icon: 'codicon-empty-window', - handler: () => { - // this.provider.openNewSessionOptions(); - }, - }, - ]; - - constructor(cdRef: ChangeDetectorRef) { - super(new State(), cdRef); - } - - public override ngAfterContentInit(): void { - super.ngAfterContentInit(); - this.update(); - this.env().subscriber.register( - this.provider.subjects.get().updated.subscribe(() => { - this.update().detectChanges(); - }), - ); - this.env().subscriber.register( - this.action.subjects.get().apply.subscribe(() => { - this.provider - .openAsNewOrigin( - new Factroy.Stream().asText().serial(this.initial.configuration).get(), - ) - .then(() => { - this.action.applied(); - this.initial.overwrite(Configuration.initial()); - }) - .catch((err: Error) => { - notifications.notify( - new Notification({ - message: err.message, - actions: [], - }), - ); - this.log().error(`Fail to apply connection to Serial: ${err.message}`); - }); - }), - ); - } - - public toggled(opened: boolean) { - this.state.toggleQuickSetup(opened); - } - - protected update(): List { - this.tailing = this.provider - .sources() - .filter((s) => s.observer !== undefined) - .map((s) => new Element(s, this.provider)); - this.offline = this.provider - .sources() - .filter((s) => s.observer === undefined) - .map((s) => new Element(s, this.provider)); - return this; - } -} -export interface List extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/serial/module.ts b/application/client/src/app/ui/views/sidebar/observe/lists/serial/module.ts deleted file mode 100644 index 8ff83a3ec..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/serial/module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { List } from './component'; -import { ElementModule } from '../../element/module'; -import { CommonObserveModule } from '../../common/module'; -import { QuickSetupModule } from '@ui/tabs/observe/origin/stream/transport/setup/quick/serial/module'; - -@NgModule({ - imports: [CommonModule, ElementModule, CommonObserveModule, QuickSetupModule], - declarations: [List], - exports: [List], -}) -export class ListModule {} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/serial/styles.less b/application/client/src/app/ui/views/sidebar/observe/lists/serial/styles.less deleted file mode 100644 index 2a8ccf7f2..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/serial/styles.less +++ /dev/null @@ -1,12 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - position: relative; - display: block; - padding: 0; - margin: 0; - p.subtitle{ - color: var(--scheme-color-3); - text-align: right; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/serial/template.html b/application/client/src/app/ui/views/sidebar/observe/lists/serial/template.html deleted file mode 100644 index 1958a0ea6..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/serial/template.html +++ /dev/null @@ -1,12 +0,0 @@ - -
- -
-
- - -
-
- - -
\ No newline at end of file diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/tcp/component.ts b/application/client/src/app/ui/views/sidebar/observe/lists/tcp/component.ts deleted file mode 100644 index ae222ec03..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/tcp/component.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Component, ChangeDetectorRef, AfterContentInit, ViewChild } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Provider } from '@service/session/dependencies/observing/implementations/tcp'; -import { Element } from '../../element/element'; -import { QuickSetup } from '@tabs/observe/origin/stream/transport/setup/quick/tcp/component'; -import { Action } from '@ui/tabs/observe/action'; -import { IButton } from '../../common/title/component'; -import { State } from '../../states/tcp'; -import { ListBase } from '../component'; -import { Configuration } from '@platform/types/observe/origin/stream/tcp'; -import { notifications, Notification } from '@ui/service/notifications'; - -import * as Factroy from '@platform/types/observe/factory'; - -@Component({ - selector: 'app-views-observed-list-tcp', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class List extends ListBase implements AfterContentInit { - @ViewChild('quicksetupref') public quickSetupRef!: QuickSetup; - - public tailing: Element[] = []; - public offline: Element[] = []; - public action: Action = new Action(); - public initial: Configuration = new Configuration(Configuration.initial(), undefined); - public buttons: IButton[] = [ - { - icon: 'codicon-tasklist', - handler: () => { - this.provider.recent(); - }, - }, - { - icon: 'codicon-empty-window', - handler: () => { - this.provider.openNewSessionOptions(); - }, - }, - ]; - - constructor(cdRef: ChangeDetectorRef) { - super(new State(), cdRef); - } - - public override ngAfterContentInit(): void { - super.ngAfterContentInit(); - this.update(); - this.env().subscriber.register( - this.provider.subjects.get().updated.subscribe(() => { - this.update().detectChanges(); - }), - ); - this.env().subscriber.register( - this.action.subjects.get().apply.subscribe(() => { - this.provider - .openAsNewOrigin( - new Factroy.Stream().asDlt().tcp(this.initial.configuration).get(), - ) - .then(() => { - this.action.applied(); - this.initial.overwrite(Configuration.initial()); - }) - .catch((err: Error) => { - notifications.notify( - new Notification({ - message: err.message, - actions: [], - }), - ); - this.log().warn(`Fail to apply connection to TCP: ${err.message}`); - }); - }), - ); - } - - public toggled(opened: boolean) { - this.state.toggleQuickSetup(opened); - } - - protected update(): List { - this.tailing = this.provider - .sources() - .filter((s) => s.observer !== undefined) - .map((s) => new Element(s, this.provider)); - this.offline = this.provider - .sources() - .filter((s) => s.observer === undefined) - .map((s) => new Element(s, this.provider)); - return this; - } -} -export interface List extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/tcp/module.ts b/application/client/src/app/ui/views/sidebar/observe/lists/tcp/module.ts deleted file mode 100644 index 0ed497def..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/tcp/module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { List } from './component'; -import { ElementModule } from '../../element/module'; -import { CommonObserveModule } from '../../common/module'; -import { QuickSetupModule } from '@ui/tabs/observe/origin/stream/transport/setup/quick/tcp/module'; - -@NgModule({ - imports: [CommonModule, ElementModule, CommonObserveModule, QuickSetupModule], - declarations: [List], - exports: [List], -}) -export class ListModule {} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/tcp/styles.less b/application/client/src/app/ui/views/sidebar/observe/lists/tcp/styles.less deleted file mode 100644 index c1c9e9f85..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/tcp/styles.less +++ /dev/null @@ -1,15 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - position: relative; - display: block; - padding: 0; - margin: 0; - p.subtitle{ - color: var(--scheme-color-3); - text-align: right; - } - div.quicksetup { - padding: 0 24px 24px 24px; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/tcp/template.html b/application/client/src/app/ui/views/sidebar/observe/lists/tcp/template.html deleted file mode 100644 index 59fa652d6..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/tcp/template.html +++ /dev/null @@ -1,12 +0,0 @@ - -
- -
-
- - -
-
- - -
\ No newline at end of file diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/udp/component.ts b/application/client/src/app/ui/views/sidebar/observe/lists/udp/component.ts deleted file mode 100644 index 6c3b3687d..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/udp/component.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Component, ChangeDetectorRef, AfterContentInit, ViewChild } from '@angular/core'; -import { Ilc, IlcInterface } from '@env/decorators/component'; -import { Provider } from '@service/session/dependencies/observing/implementations/udp'; -import { Element } from '../../element/element'; -import { QuickSetup } from '@tabs/observe/origin/stream/transport/setup/quick/udp/component'; -import { Action } from '@ui/tabs/observe/action'; -import { IButton } from '../../common/title/component'; -import { State } from '../../states/udp'; -import { ListBase } from '../component'; -import { Configuration } from '@platform/types/observe/origin/stream/udp'; -import { notifications, Notification } from '@ui/service/notifications'; - -import * as Factroy from '@platform/types/observe/factory'; - -@Component({ - selector: 'app-views-observed-list-udp', - templateUrl: './template.html', - styleUrls: ['./styles.less'], - standalone: false, -}) -@Ilc() -export class List extends ListBase implements AfterContentInit { - @ViewChild('quicksetupref') public quickSetupRef!: QuickSetup; - - public tailing: Element[] = []; - public offline: Element[] = []; - public action: Action = new Action(); - public initial: Configuration = new Configuration(Configuration.initial(), undefined); - public buttons: IButton[] = [ - { - icon: 'codicon-tasklist', - handler: () => { - this.provider.recent(); - }, - }, - { - icon: 'codicon-empty-window', - handler: () => { - this.provider.openNewSessionOptions(); - }, - }, - ]; - - constructor(cdRef: ChangeDetectorRef) { - super(new State(), cdRef); - } - - public override ngAfterContentInit(): void { - super.ngAfterContentInit(); - this.update(); - this.env().subscriber.register( - this.provider.subjects.get().updated.subscribe(() => { - this.update().detectChanges(); - }), - ); - this.env().subscriber.register( - this.action.subjects.get().apply.subscribe(() => { - this.provider - .openAsNewOrigin( - new Factroy.Stream().asDlt().udp(this.initial.configuration).get(), - ) - .then(() => { - this.action.applied(); - this.initial.overwrite(Configuration.initial()); - }) - .catch((err: Error) => { - notifications.notify( - new Notification({ - message: err.message, - actions: [], - }), - ); - this.log().error(`Fail to apply connection to UDP: ${err.message}`); - }); - }), - ); - } - - public toggled(opened: boolean) { - this.state.toggleQuickSetup(opened); - } - - protected update(): List { - this.tailing = this.provider - .sources() - .filter((s) => s.observer !== undefined) - .map((s) => new Element(s, this.provider)); - this.offline = this.provider - .sources() - .filter((s) => s.observer === undefined) - .map((s) => new Element(s, this.provider)); - return this; - } -} -export interface List extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/udp/module.ts b/application/client/src/app/ui/views/sidebar/observe/lists/udp/module.ts deleted file mode 100644 index f1b92a3d7..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/udp/module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { List } from './component'; -import { ElementModule } from '../../element/module'; -import { CommonObserveModule } from '../../common/module'; -import { QuickSetupModule } from '@ui/tabs/observe/origin/stream/transport/setup/quick/udp/module'; - -@NgModule({ - imports: [CommonModule, ElementModule, CommonObserveModule, QuickSetupModule], - declarations: [List], - exports: [List], -}) -export class ListModule {} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/udp/styles.less b/application/client/src/app/ui/views/sidebar/observe/lists/udp/styles.less deleted file mode 100644 index c1c9e9f85..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/udp/styles.less +++ /dev/null @@ -1,15 +0,0 @@ -@import '../../../../../styles/variables.less'; - -:host { - position: relative; - display: block; - padding: 0; - margin: 0; - p.subtitle{ - color: var(--scheme-color-3); - text-align: right; - } - div.quicksetup { - padding: 0 24px 24px 24px; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/lists/udp/template.html b/application/client/src/app/ui/views/sidebar/observe/lists/udp/template.html deleted file mode 100644 index 818a546ed..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/lists/udp/template.html +++ /dev/null @@ -1,12 +0,0 @@ - -
- -
-
- - -
-
- - -
\ No newline at end of file diff --git a/application/client/src/app/ui/views/sidebar/observe/module.ts b/application/client/src/app/ui/views/sidebar/observe/module.ts index 7631400b7..24ad547ee 100644 --- a/application/client/src/app/ui/views/sidebar/observe/module.ts +++ b/application/client/src/app/ui/views/sidebar/observe/module.ts @@ -9,11 +9,7 @@ import { MatMenuModule } from '@angular/material/menu'; import { MatIconModule } from '@angular/material/icon'; import { MatDividerModule } from '@angular/material/divider'; import { AttachSourceMenuModule } from '@elements/menu.attachsource/module'; -import { ListModule as FileListModule } from './lists/file/module'; -import { ListModule as ProcessListModule } from './lists/process/module'; -import { ListModule as SerialListModule } from './lists/serial/module'; -import { ListModule as TcpListModule } from './lists/tcp/module'; -import { ListModule as UdpListModule } from './lists/udp/module'; +import { Operation } from './operation/component'; @NgModule({ imports: [ @@ -22,17 +18,12 @@ import { ListModule as UdpListModule } from './lists/udp/module'; MatButtonModule, MatCardModule, MatExpansionModule, - FileListModule, - ProcessListModule, - SerialListModule, - TcpListModule, - UdpListModule, MatMenuModule, MatIconModule, MatDividerModule, AttachSourceMenuModule, ], - declarations: [Observed], + declarations: [Observed, Operation], exports: [Observed], bootstrap: [Observed], }) diff --git a/application/client/src/app/ui/views/sidebar/observe/operation/component.ts b/application/client/src/app/ui/views/sidebar/observe/operation/component.ts new file mode 100644 index 000000000..052677e21 --- /dev/null +++ b/application/client/src/app/ui/views/sidebar/observe/operation/component.ts @@ -0,0 +1,84 @@ +import { Component, Input, AfterContentInit, ChangeDetectorRef } from '@angular/core'; +import { Ilc, IlcInterface } from '@env/decorators/component'; +import { State } from '@service/session/dependencies/observing/operation'; +import { ObserveOperation } from '@service/session/dependencies/stream'; +import { ChangesDetector } from '@ui/env/extentions/changes'; +import { getSourceColor } from '@ui/styles/colors'; +import { Stream } from '@service/session/dependencies/stream'; + +@Component({ + selector: 'app-views-observed-operation', + templateUrl: './template.html', + styleUrls: ['./styles.less'], + standalone: false, +}) +@Ilc() +export class Operation extends ChangesDetector implements AfterContentInit { + @Input() operation!: ObserveOperation; + @Input() stream!: Stream; + + public title!: string; + public subtitle: string | undefined = undefined; + public state!: State; + public color!: string; + + protected update() { + const descriptor = this.operation.getDescriptor(); + this.state = this.operation.state; + if (descriptor) { + this.title = descriptor.s_desc ? descriptor.s_desc : descriptor.source.name; + this.subtitle = descriptor.p_desc ? descriptor.p_desc : descriptor.parser.name; + } else { + this.log().warn(`Descriptor of operation isn't available`); + this.title = this.operation.getOrigin().getTitle(); + this.subtitle = undefined; + } + this.color = this.getSourceColor(); + } + + protected getSourceColor(): string { + const id = this.stream.observe().descriptions.id(this.operation.uuid); + return id === undefined ? '' : getSourceColor(id); + } + + constructor(cdRef: ChangeDetectorRef) { + super(cdRef); + } + + public get State() { + return State; + } + + public ngAfterContentInit(): void { + this.update(); + this.env().subscriber.register( + this.operation.stateUpdateEvent.subscribe(() => { + this.update(); + this.detectChanges(); + }), + ); + } + + public select() { + this.stream.sde.selecting().select(this.operation.uuid); + } + + public stop() { + if (!this.operation.isRunning()) { + return; + } + this.operation.abort().catch((err: Error) => { + this.log().error(`Fail to stop operation: ${err.message}`); + }); + } + + public restart() { + if (this.operation.isRunning()) { + return; + } + this.operation.restart().catch((err: Error) => { + this.log().error(`Fail to restart operation: ${err.message}`); + }); + } +} +export interface Operation extends IlcInterface {} diff --git a/application/client/src/app/ui/views/sidebar/observe/operation/styles.less b/application/client/src/app/ui/views/sidebar/observe/operation/styles.less new file mode 100644 index 000000000..5e48b4bef --- /dev/null +++ b/application/client/src/app/ui/views/sidebar/observe/operation/styles.less @@ -0,0 +1,45 @@ +@import '../../../../styles/variables.less'; + +:host { + position: relative; + display: flex; + flex-direction: row; + padding: 0 12px; + align-items: center; + &:hover { + background: var(--scheme-color-5); + } + & div.desc { + flex: auto; + & span.source-color { + position: absolute; + width: 2px; + height: 100%; + left: 0px; + border-radius: 1px; + } + & p.title { + color: var(--scheme-color-2); + } + & p.subtitle { + color: var(--scheme-color-3); + } + & p.status { + &.started { + color: var(--scheme-color-accent); + } + &.running { + color: var(--scheme-color-accent); + } + &.finished { + color: var(--scheme-color-2); + } + &.aborted { + color: var(--scheme-color-error-light); + } + } + } + & div.buttons { + + } +} diff --git a/application/client/src/app/ui/views/sidebar/observe/operation/template.html b/application/client/src/app/ui/views/sidebar/observe/operation/template.html new file mode 100644 index 000000000..e2a8f7139 --- /dev/null +++ b/application/client/src/app/ui/views/sidebar/observe/operation/template.html @@ -0,0 +1,14 @@ +
+ +

{{title}}

+

+ {{subtitle}} +

+

{{state}}

+
+
+ stop + loop +
diff --git a/application/client/src/app/ui/views/sidebar/observe/states/files.ts b/application/client/src/app/ui/views/sidebar/observe/states/files.ts deleted file mode 100644 index 217f0e4c6..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/states/files.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from './state'; -import { unique } from '@platform/env/sequence'; - -export const KEY: string = unique(); - -export class State extends Base { - - public key(): string { - return KEY; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/states/process.ts b/application/client/src/app/ui/views/sidebar/observe/states/process.ts deleted file mode 100644 index 4210b3fdc..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/states/process.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Base } from './state'; -import { unique } from '@platform/env/sequence'; - -export const KEY: string = unique(); - -export class State extends Base { - public override quicksetup: boolean = true; - - public key(): string { - return KEY; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/states/serial.ts b/application/client/src/app/ui/views/sidebar/observe/states/serial.ts deleted file mode 100644 index 485e5bf7e..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/states/serial.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Base } from './state'; -import { unique } from '@platform/env/sequence'; - -export const KEY: string = unique(); - -export class State extends Base { - public key(): string { - return KEY; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/states/state.ts b/application/client/src/app/ui/views/sidebar/observe/states/state.ts deleted file mode 100644 index 482b33b76..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/states/state.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Destroy } from '@platform/types/env/types'; - -export abstract class Base implements Destroy { - public quicksetup: boolean = false; - - public toggleQuickSetup(quicksetup?: boolean) { - if (quicksetup !== undefined) { - this.quicksetup = quicksetup; - } else { - this.quicksetup = !this.quicksetup; - } - } - - public destroy(): void { - // - } - - public abstract key(): string; -} diff --git a/application/client/src/app/ui/views/sidebar/observe/states/tcp.ts b/application/client/src/app/ui/views/sidebar/observe/states/tcp.ts deleted file mode 100644 index 485e5bf7e..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/states/tcp.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Base } from './state'; -import { unique } from '@platform/env/sequence'; - -export const KEY: string = unique(); - -export class State extends Base { - public key(): string { - return KEY; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/states/udp.ts b/application/client/src/app/ui/views/sidebar/observe/states/udp.ts deleted file mode 100644 index 485e5bf7e..000000000 --- a/application/client/src/app/ui/views/sidebar/observe/states/udp.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Base } from './state'; -import { unique } from '@platform/env/sequence'; - -export const KEY: string = unique(); - -export class State extends Base { - public key(): string { - return KEY; - } -} diff --git a/application/client/src/app/ui/views/sidebar/observe/template.html b/application/client/src/app/ui/views/sidebar/observe/template.html index 7710be917..ee7c0d8f0 100644 --- a/application/client/src/app/ui/views/sidebar/observe/template.html +++ b/application/client/src/app/ui/views/sidebar/observe/template.html @@ -1,29 +1,42 @@
Observing Sources - ({{session.observed.count()}}) + ({{operations.length}})
- - - - {{provider.value.panels.list.name}} - {{provider.value.panels.list.desc}} - -
- -
- -
-
- + + + Active + +
+ + +
+
+ + + Inactive + +
+ + +
+
diff --git a/application/client/src/app/ui/views/sidebar/search/component.ts b/application/client/src/app/ui/views/sidebar/search/component.ts index 91a884a8e..606631075 100644 --- a/application/client/src/app/ui/views/sidebar/search/component.ts +++ b/application/client/src/app/ui/views/sidebar/search/component.ts @@ -54,20 +54,15 @@ export class Filters extends ChangesDetector implements OnDestroy, AfterContentI this._focused = false; } @HostListener('contextmenu', ['$event']) onContextMenu(event: MouseEvent) { - const items: IMenuItem[] = [ - { - caption: `Clear recent history`, - handler: () => { - this.log().debug(`Not implemented yet`); - }, - }, - ]; + const items: IMenuItem[] = []; this.providers.injectGeneralMenuItems(items); - contextmenu.show({ - items, - x: event.pageX, - y: event.pageY, - }); + if (items.length > 0) { + contextmenu.show({ + items, + x: event.pageX, + y: event.pageY, + }); + } dom.stop(event); } diff --git a/application/client/src/app/ui/views/workspace/sde/state.ts b/application/client/src/app/ui/views/workspace/sde/state.ts index 50967b31c..20e4e6536 100644 --- a/application/client/src/app/ui/views/workspace/sde/state.ts +++ b/application/client/src/app/ui/views/workspace/sde/state.ts @@ -2,7 +2,6 @@ import { Session } from '@service/session'; import { IlcInterface } from '@env/decorators/component'; import { ChangesDetector } from '@ui/env/extentions/changes'; import { ObserveOperation } from '@service/session/dependencies/stream'; -import { IOriginDetails } from '@platform/types/observe'; import { Destroyable } from '@platform/types/life/destroyable'; import { Notification } from '@ui/service/notifications'; import { getSourceColor } from '@ui/styles/colors'; @@ -54,10 +53,6 @@ export class State implements Destroyable { return id === undefined ? '' : getSourceColor(id); } - public desc(source: ObserveOperation): IOriginDetails { - return source.asOrigin().desc(); - } - public send(data: string): Promise { const selected = this.session.stream.sde.selecting().get(); if (selected === undefined) { diff --git a/application/client/src/app/ui/views/workspace/sde/template.html b/application/client/src/app/ui/views/workspace/sde/template.html index 552bf2466..b8eaf663e 100644 --- a/application/client/src/app/ui/views/workspace/sde/template.html +++ b/application/client/src/app/ui/views/workspace/sde/template.html @@ -1,8 +1,11 @@
>> - + (panel)="panel()" + >

Send command/data to:

- -
\ No newline at end of file + diff --git a/application/client/src/app/ui/views/workspace/title/state.ts b/application/client/src/app/ui/views/workspace/title/state.ts index 73df2cc93..151ee1439 100644 --- a/application/client/src/app/ui/views/workspace/title/state.ts +++ b/application/client/src/app/ui/views/workspace/title/state.ts @@ -60,21 +60,25 @@ export class State implements Destroyable { return; } this.flags.sde = this.session.stream.sde.isAvailable(); - const sources = this.session.stream.observe().sources(); - if (sources.length === 0) { + const operations = this.session.stream.observe().operations(); + if (operations.length === 0) { this.states.sde = false; this.title = 'no sources are bound with session'; - } else if (sources.length === 1) { + } else if (operations.length === 1) { this.states.sde = !this.session.stream.sde.visibility().hidden(); this.title = ((): string => { - const observe = sources[0].observe; - const desc = observe.origin.desc(); - return `${desc.major}: ${desc.minor}`; + const descriptor = operations[0].getDescriptor(); + if (descriptor) { + return `${descriptor.s_desc} (${descriptor.p_desc})`; + } else { + const origin = operations[0].getOrigin(); + return origin.getTitle(); + } })(); } else { - const running = sources.filter((s) => s.observer !== undefined).length; - this.title = `multiple ${sources.length} sources; ${running} running; ${ - sources.length - running + const running = operations.filter((opr) => opr.isRunning()).length; + this.title = `multiple ${operations.length} sources; ${running} running; ${ + operations.length - running } stopped`; } this.safe().updateRefComp(); diff --git a/application/holder/src/env/fs/index.ts b/application/holder/src/env/fs/index.ts index fee261da8..49a485653 100644 --- a/application/holder/src/env/fs/index.ts +++ b/application/holder/src/env/fs/index.ts @@ -1,5 +1,5 @@ import { File, Stat, getFileExtention } from 'platform/types/files'; -import { FileType, extname } from 'platform/types/observe/types/file'; +import { FileType, extname } from 'platform/types/files'; import { error } from 'platform/log/utils'; import { unbound } from '@service/unbound'; @@ -148,4 +148,3 @@ export async function getFileTypeByFilename(filename: string): Promise return FileType.Text; } } - diff --git a/application/holder/src/loaders/services.ts b/application/holder/src/loaders/services.ts index 8c9e21ddb..fefa666b7 100644 --- a/application/holder/src/loaders/services.ts +++ b/application/holder/src/loaders/services.ts @@ -18,3 +18,4 @@ import '@service/cli'; import '@service/menu'; import '@service/unbound'; import '@service/github'; +import '@service/components'; diff --git a/application/holder/src/register/services.ts b/application/holder/src/register/services.ts index 9f8ae7607..f900e5df8 100644 --- a/application/holder/src/register/services.ts +++ b/application/holder/src/register/services.ts @@ -30,6 +30,10 @@ export const services: { [key: string]: Inputs } = { name: 'unbound', uuid: v4(), }, + components: { + name: 'components', + uuid: v4(), + }, storage: { name: 'storage', uuid: v4(), diff --git a/application/holder/src/service/actions/open.file.ts b/application/holder/src/service/actions/open.file.ts index aa8b4896e..7bb118919 100644 --- a/application/holder/src/service/actions/open.file.ts +++ b/application/holder/src/service/actions/open.file.ts @@ -1,4 +1,4 @@ -import { FileType } from 'platform/types/observe/types/file'; +import { FileType } from 'platform/types/files'; import * as Requests from 'platform/ipc/request'; diff --git a/application/holder/src/service/actions/open.folder.ts b/application/holder/src/service/actions/open.folder.ts index 0c041a0c8..193e9fb6a 100644 --- a/application/holder/src/service/actions/open.folder.ts +++ b/application/holder/src/service/actions/open.folder.ts @@ -1,4 +1,4 @@ -import { FileType } from 'platform/types/observe/types/file'; +import { FileType } from 'platform/types/files'; import * as Requests from 'platform/ipc/request'; diff --git a/application/holder/src/service/actions/stream.ts b/application/holder/src/service/actions/stream.ts index d33c19cde..c15a74949 100644 --- a/application/holder/src/service/actions/stream.ts +++ b/application/holder/src/service/actions/stream.ts @@ -1,15 +1,11 @@ -import * as $ from 'platform/types/observe'; - import * as Requests from 'platform/ipc/request'; +import { Ident } from 'platform/types/bindings'; -export function handler( - protocol: $.Parser.Protocol, - source?: $.Origin.Stream.Stream.Source, -): Promise { +export function handler(parser: Ident | undefined, source: Ident | undefined): Promise { return new Promise((resolve, reject) => { Requests.IpcRequest.send( Requests.Actions.Stream.Response, - new Requests.Actions.Stream.Request({ protocol, source }), + new Requests.Actions.Stream.Request({ parser, source }), ).then((response) => { if (response.error === undefined) { return resolve(); diff --git a/application/holder/src/service/bridge/file/select.ts b/application/holder/src/service/bridge/file/select.ts index dd038a9b1..6cc316e36 100644 --- a/application/holder/src/service/bridge/file/select.ts +++ b/application/holder/src/service/bridge/file/select.ts @@ -2,7 +2,7 @@ import { CancelablePromise } from 'platform/env/promise'; import { Logger } from 'platform/log'; import { electron } from '@service/electron'; import { File } from 'platform/types/files'; -import { FileType } from 'platform/types/observe/types/file'; +import { FileType } from 'platform/types/files'; import { getFileEntities } from '@env/fs'; import * as Requests from 'platform/ipc/request'; diff --git a/application/holder/src/service/bridge/folder/select.ts b/application/holder/src/service/bridge/folder/select.ts index af954dacb..78d78397b 100644 --- a/application/holder/src/service/bridge/folder/select.ts +++ b/application/holder/src/service/bridge/folder/select.ts @@ -2,7 +2,7 @@ import { CancelablePromise } from 'platform/env/promise'; import { Logger } from 'platform/log'; import { electron } from '@service/electron'; import { File } from 'platform/types/files'; -import { FileType } from 'platform/types/observe/types/file'; +import { FileType } from 'platform/types/files'; import { getFileEntities, getFilesFromFolder } from '@env/fs'; import * as Requests from 'platform/ipc/request'; diff --git a/application/holder/src/service/cli.ts b/application/holder/src/service/cli.ts index 5a4c2604b..a9c00043c 100644 --- a/application/holder/src/service/cli.ts +++ b/application/holder/src/service/cli.ts @@ -18,7 +18,6 @@ import * as Actions from './cli/index'; import * as Events from 'platform/ipc/event'; import * as Requests from 'platform/ipc/request'; import * as fs from 'fs'; -import * as $ from 'platform/types/observe'; const UNIX_LOCAL_BIN = '/usr/local/bin'; const UNIX_SYMLINK_PATH = `${UNIX_LOCAL_BIN}/cm`; @@ -32,13 +31,13 @@ export class Service extends Implementation { protected args: string[] = []; private _available: boolean | undefined; - private readonly _state: { - sessions: string[]; - parser: $.Parser.Protocol; - } = { - sessions: [], - parser: $.Parser.Protocol.Text, - }; + // private readonly _state: { + // sessions: string[]; + // parser: $.Parser.Protocol; + // } = { + // sessions: [], + // parser: $.Parser.Protocol.Text, + // }; public override ready(): Promise { this.log().debug(`Incoming arguments:\n\t${process.argv.join('\n\t')}`); @@ -106,25 +105,25 @@ export class Service extends Implementation { return Promise.resolve(); } - public state(): { - sessions(sessions?: string[]): string[]; - parser(parser?: $.Parser.Protocol): $.Parser.Protocol; - } { - return { - sessions: (sessions?: string[]): string[] => { - if (sessions !== undefined) { - this._state.sessions = sessions; - } - return this._state.sessions; - }, - parser: (parser?: $.Parser.Protocol): $.Parser.Protocol => { - if (parser !== undefined) { - this._state.parser = parser; - } - return this._state.parser; - }, - }; - } + // public state(): { + // sessions(sessions?: string[]): string[]; + // parser(parser?: $.Parser.Protocol): $.Parser.Protocol; + // } { + // return { + // sessions: (sessions?: string[]): string[] => { + // if (sessions !== undefined) { + // this._state.sessions = sessions; + // } + // return this._state.sessions; + // }, + // parser: (parser?: $.Parser.Protocol): $.Parser.Protocol => { + // if (parser !== undefined) { + // this._state.parser = parser; + // } + // return this._state.parser; + // }, + // }; + // } public support(): { install(): Promise; diff --git a/application/holder/src/service/cli/open.ts b/application/holder/src/service/cli/open.ts index f6843f623..77af8b602 100644 --- a/application/holder/src/service/cli/open.ts +++ b/application/holder/src/service/cli/open.ts @@ -1,15 +1,13 @@ import { CLIAction, Type } from './action'; import { Service } from '@service/cli'; import { getFileEntities } from '@env/fs'; -import { FileType } from 'platform/types/observe/types/file'; +import { FileType } from 'platform/types/files'; import { globSync } from 'glob'; import { error } from 'platform/log/utils'; import * as fs from 'fs'; import * as path from 'path'; import * as Requests from 'platform/ipc/request'; -import * as Factory from 'platform/types/observe/factory'; -import * as Parser from 'platform/types/observe/parser'; export class Action extends CLIAction { protected files: string[] = []; @@ -62,42 +60,43 @@ export class Action extends CLIAction { if (files.length === 0) { return; } - const observe = - files.length === 1 - ? new Factory.File().file(files[0].filename).get() - : new Factory.Concat().files(files.map((f) => f.filename)).get(); - const types: FileType[] = []; - files.forEach((file) => { - if (types.includes(file.type)) { - return; - } - types.push(file.type); - }); - if (types.length === 1) { - if (types[0] === FileType.Text) { - observe.parser.change( - new Parser.Text.Configuration(Parser.Text.Configuration.initial(), undefined), - ); - } - } - return new Promise((resolve, _reject) => { - Requests.IpcRequest.send( - Requests.Cli.Observe.Response, - new Requests.Cli.Observe.Request({ - observe: [observe.sterilized()], - }), - ) - .then((response) => { - if (response.session === undefined) { - return; - } - cli.state().sessions([response.session]); - }) - .catch((err: Error) => { - cli.log().error(`Fail apply open-action: ${err.message}`); - }) - .finally(resolve); - }); + // const observe = + // files.length === 1 + // ? new Factory.File().file(files[0].filename).get() + // : new Factory.Concat().files(files.map((f) => f.filename)).get(); + // const types: FileType[] = []; + // files.forEach((file) => { + // if (types.includes(file.type)) { + // return; + // } + // types.push(file.type); + // }); + // if (types.length === 1) { + // if (types[0] === FileType.Text) { + // observe.parser.change( + // new Parser.Text.Configuration(Parser.Text.Configuration.initial(), undefined), + // ); + // } + // } + // return new Promise((resolve, _reject) => { + // Requests.IpcRequest.send( + // Requests.Cli.Observe.Response, + // new Requests.Cli.Observe.Request({ + // observe: [observe.sterilized()], + // }), + // ) + // .then((response) => { + // if (response.session === undefined) { + // return; + // } + // cli.state().sessions([response.session]); + // }) + // .catch((err: Error) => { + // cli.log().error(`Fail apply open-action: ${err.message}`); + // }) + // .finally(resolve); + // }); + return Promise.reject(new Error(`Not implemented!`)); } public type(): Type { diff --git a/application/holder/src/service/cli/parser.ts b/application/holder/src/service/cli/parser.ts index 41c84c12c..ccbfcc5c7 100644 --- a/application/holder/src/service/cli/parser.ts +++ b/application/holder/src/service/cli/parser.ts @@ -1,32 +1,31 @@ import { CLIAction, Type } from './action'; import { Service } from '@service/cli'; -import { Protocol } from 'platform/types/observe/parser'; export class Action extends CLIAction { - protected parser: Protocol | undefined; + // protected parser: Protocol | undefined; protected error: Error[] = []; public argument(_target: string | undefined, _cwd: string, arg: string): string { - switch (arg.toLowerCase()) { - case Protocol.Dlt.toLowerCase(): - this.parser = Protocol.Dlt; - return arg; - case Protocol.SomeIp.toLowerCase(): - this.parser = Protocol.SomeIp; - return arg; - case Protocol.Text.toLowerCase(): - this.parser = Protocol.Text; - return arg; - } - this.error.push( - new Error( - `Invalid value of parser: ${arg}. Available: ${[ - Protocol.Dlt, - Protocol.SomeIp, - Protocol.Text, - ].join(', ')}.`, - ), - ); + // switch (arg.toLowerCase()) { + // case Protocol.Dlt.toLowerCase(): + // this.parser = Protocol.Dlt; + // return arg; + // case Protocol.SomeIp.toLowerCase(): + // this.parser = Protocol.SomeIp; + // return arg; + // case Protocol.Text.toLowerCase(): + // this.parser = Protocol.Text; + // return arg; + // } + // this.error.push( + // new Error( + // `Invalid value of parser: ${arg}. Available: ${[ + // Protocol.Dlt, + // Protocol.SomeIp, + // Protocol.Text, + // ].join(', ')}.`, + // ), + // ); return ''; } @@ -35,20 +34,21 @@ export class Action extends CLIAction { } public execute(cli: Service): Promise { - if (this.error.length > 0) { - return Promise.reject( - new Error( - `Handler cannot be executed, because errors: \n${this.error - .map((e) => e.message) - .join('\n')}`, - ), - ); - } - if (this.parser === undefined) { - return Promise.resolve(); - } - cli.state().parser(this.parser); - return Promise.resolve(); + // if (this.error.length > 0) { + // return Promise.reject( + // new Error( + // `Handler cannot be executed, because errors: \n${this.error + // .map((e) => e.message) + // .join('\n')}`, + // ), + // ); + // } + // if (this.parser === undefined) { + // return Promise.resolve(); + // } + // cli.state().parser(this.parser); + // return Promise.resolve(); + return Promise.reject(new Error(`Not implemented!`)); } public type(): Type { @@ -56,6 +56,7 @@ export class Action extends CLIAction { } public defined(): boolean { - return this.parser !== undefined; + return false; + // return this.parser !== undefined; } } diff --git a/application/holder/src/service/cli/search.ts b/application/holder/src/service/cli/search.ts index 6db3469e1..c8c1f4118 100644 --- a/application/holder/src/service/cli/search.ts +++ b/application/holder/src/service/cli/search.ts @@ -37,27 +37,28 @@ export class Action extends CLIAction { if (!this.defined()) { return Promise.resolve(); } - if (cli.state().sessions().length === 0) { - return Promise.resolve(); - } - return new Promise((resolve, _reject) => { - Requests.IpcRequest.send( - Requests.Cli.Search.Response, - new Requests.Cli.Search.Request({ - sessions: cli.state().sessions(), - filters: this.filters, - }), - ) - .then((response) => { - if (response.error !== undefined) { - cli.log().error(`Fail apply search via CLI: ${response.error}`); - } - }) - .catch((err: Error) => { - cli.log().error(`Fail apply CLI.Search: ${err.message}`); - }) - .finally(resolve); - }); + // if (cli.state().sessions().length === 0) { + // return Promise.resolve(); + // } + // return new Promise((resolve, _reject) => { + // Requests.IpcRequest.send( + // Requests.Cli.Search.Response, + // new Requests.Cli.Search.Request({ + // sessions: cli.state().sessions(), + // filters: this.filters, + // }), + // ) + // .then((response) => { + // if (response.error !== undefined) { + // cli.log().error(`Fail apply search via CLI: ${response.error}`); + // } + // }) + // .catch((err: Error) => { + // cli.log().error(`Fail apply CLI.Search: ${err.message}`); + // }) + // .finally(resolve); + // }); + return Promise.reject(new Error(`Not implemented!`)); } public type(): Type { diff --git a/application/holder/src/service/cli/stream.ts b/application/holder/src/service/cli/stream.ts index 50a51fccd..992cc1b73 100644 --- a/application/holder/src/service/cli/stream.ts +++ b/application/holder/src/service/cli/stream.ts @@ -2,186 +2,184 @@ import { CLIAction, Type } from './action'; import { Service } from '@service/cli'; import * as Requests from 'platform/ipc/request'; -import * as Factory from 'platform/types/observe/factory'; -import * as $ from 'platform/types/observe'; -function serial(settings: string): $.Origin.Stream.Stream.Serial.IConfiguration | Error { - settings = settings.replace(/\s/gi, ''); - const parts = settings.split(';'); - if (parts.length !== 6) { - return new Error( - `Expecting definition for path; baud_rate; data_bits; flow_control; parity; stop_bits. Use -h (--help) for more details`, - ); - } - let error: Error | undefined; - const parameters: $.Origin.Stream.Stream.Serial.IConfiguration = { - path: '', - baud_rate: -1, - data_bits: -1, - flow_control: -1, - parity: -1, - stop_bits: -1, - send_data_delay: -1, - exclusive: true, - }; - const keys = [ - 'path', - 'baud_rate', - 'data_bits', - 'flow_control', - 'parity', - 'stop_bits', - 'send_data_delay', - 'exclusive', - ]; - parts.forEach((p, i) => { - if (error instanceof Error) { - return; - } - if (i === 0 && p.trim().length === 0) { - error = new Error(`Path has invalid value.`); - return; - } - if (i === 0) { - (parameters as unknown as { [key: string]: string | number | boolean })[keys[i]] = p; - } else if (i === keys.length - 1) { - (parameters as unknown as { [key: string]: string | number | boolean })[keys[i]] = - typeof p === 'boolean' - ? p - : typeof p === 'string' - ? p === 'true' - ? true - : false - : typeof p === 'number' - ? p === 1 - ? true - : false - : true; - } else { - const value = parseInt(p, 10); - if (isNaN(value) || !isFinite(value)) { - error = new Error(`Parameter "${keys[i]}" has invalid value.`); - return; - } - (parameters as unknown as { [key: string]: string | number })[keys[i]] = value; - } - }); - if (error instanceof Error) { - return error; - } - return parameters; -} +// function serial(settings: string): $.Origin.Stream.Stream.Serial.IConfiguration | Error { +// settings = settings.replace(/\s/gi, ''); +// const parts = settings.split(';'); +// if (parts.length !== 6) { +// return new Error( +// `Expecting definition for path; baud_rate; data_bits; flow_control; parity; stop_bits. Use -h (--help) for more details`, +// ); +// } +// let error: Error | undefined; +// const parameters: $.Origin.Stream.Stream.Serial.IConfiguration = { +// path: '', +// baud_rate: -1, +// data_bits: -1, +// flow_control: -1, +// parity: -1, +// stop_bits: -1, +// send_data_delay: -1, +// exclusive: true, +// }; +// const keys = [ +// 'path', +// 'baud_rate', +// 'data_bits', +// 'flow_control', +// 'parity', +// 'stop_bits', +// 'send_data_delay', +// 'exclusive', +// ]; +// parts.forEach((p, i) => { +// if (error instanceof Error) { +// return; +// } +// if (i === 0 && p.trim().length === 0) { +// error = new Error(`Path has invalid value.`); +// return; +// } +// if (i === 0) { +// (parameters as unknown as { [key: string]: string | number | boolean })[keys[i]] = p; +// } else if (i === keys.length - 1) { +// (parameters as unknown as { [key: string]: string | number | boolean })[keys[i]] = +// typeof p === 'boolean' +// ? p +// : typeof p === 'string' +// ? p === 'true' +// ? true +// : false +// : typeof p === 'number' +// ? p === 1 +// ? true +// : false +// : true; +// } else { +// const value = parseInt(p, 10); +// if (isNaN(value) || !isFinite(value)) { +// error = new Error(`Parameter "${keys[i]}" has invalid value.`); +// return; +// } +// (parameters as unknown as { [key: string]: string | number })[keys[i]] = value; +// } +// }); +// if (error instanceof Error) { +// return error; +// } +// return parameters; +// } -function udp(settings: string): $.Origin.Stream.Stream.UDP.IConfiguration | Error { - settings = settings.replace(/\s/gi, ''); - const parts = settings.split('|'); - if (parts.length !== 2) { - return new Error( - `Expecting definition for addr and multicast splitted with "|". Use -h (--help) for more details`, - ); - } - if (parts[0].length === 0) { - return new Error(`Fail to find addr to connect. Use -h (--help) for more details`); - } - if (parts[1].length === 0) { - return new Error(`Fail to find multicast defenitions. Use -h (--help) for more details`); - } - let error: Error | undefined; - const multicast = parts[1] - .split(';') - .map((pair) => { - if (pair.trim().length === 0) { - return undefined; - } - if (error !== undefined) { - return { - multiaddr: '', - interface: undefined, - }; - } - const pairs = pair.split(','); - if (pairs.length !== 1 && pairs.length !== 2) { - error = new Error( - `Each multicast defenition should include address and interface (or at least mutlicast address). Use -h (--help) for more details `, - ); - } - if (pairs.length === 1 && pairs[0].length === 0) { - error = new Error( - `Each multicast defenition should include at least address. Use -h (--help) for more details `, - ); - } - if (pairs.length === 2 && pairs[0].length === 0 && pairs[1].length === 0) { - error = new Error( - `Each multicast defenition should include address and interface. Use -h (--help) for more details `, - ); - } - return { - multiaddr: pairs[0], - interface: pairs.length === 1 ? undefined : pairs[1], - }; - }) - .filter( - (m: $.Origin.Stream.Stream.UDP.Multicast | undefined) => m !== undefined, - ) as $.Origin.Stream.Stream.UDP.Multicast[]; - if (error instanceof Error) { - return error; - } - return { - bind_addr: parts[0], - multicast, - }; -} +// function udp(settings: string): $.Origin.Stream.Stream.UDP.IConfiguration | Error { +// settings = settings.replace(/\s/gi, ''); +// const parts = settings.split('|'); +// if (parts.length !== 2) { +// return new Error( +// `Expecting definition for addr and multicast splitted with "|". Use -h (--help) for more details`, +// ); +// } +// if (parts[0].length === 0) { +// return new Error(`Fail to find addr to connect. Use -h (--help) for more details`); +// } +// if (parts[1].length === 0) { +// return new Error(`Fail to find multicast defenitions. Use -h (--help) for more details`); +// } +// let error: Error | undefined; +// const multicast = parts[1] +// .split(';') +// .map((pair) => { +// if (pair.trim().length === 0) { +// return undefined; +// } +// if (error !== undefined) { +// return { +// multiaddr: '', +// interface: undefined, +// }; +// } +// const pairs = pair.split(','); +// if (pairs.length !== 1 && pairs.length !== 2) { +// error = new Error( +// `Each multicast defenition should include address and interface (or at least mutlicast address). Use -h (--help) for more details `, +// ); +// } +// if (pairs.length === 1 && pairs[0].length === 0) { +// error = new Error( +// `Each multicast defenition should include at least address. Use -h (--help) for more details `, +// ); +// } +// if (pairs.length === 2 && pairs[0].length === 0 && pairs[1].length === 0) { +// error = new Error( +// `Each multicast defenition should include address and interface. Use -h (--help) for more details `, +// ); +// } +// return { +// multiaddr: pairs[0], +// interface: pairs.length === 1 ? undefined : pairs[1], +// }; +// }) +// .filter( +// (m: $.Origin.Stream.Stream.UDP.Multicast | undefined) => m !== undefined, +// ) as $.Origin.Stream.Stream.UDP.Multicast[]; +// if (error instanceof Error) { +// return error; +// } +// return { +// bind_addr: parts[0], +// multicast, +// }; +// } -function getObserveFactory( - target: string | undefined, - cwd: string, - arg: string, -): Factory.Stream | Error { - if (target === 'stdout') { - if (arg.trim() !== '') { - return new Factory.Stream().process({ - command: arg, - cwd, - envs: {}, - }); - } else { - return new Error(`Command to spawn cannot be empty`); - } - } else if (target === 'serial') { - const settings = serial(arg); - if (settings instanceof Error) { - return settings; - } else { - return new Factory.Stream().serial(settings); - } - } else if (target === 'tcp') { - if (arg.trim() !== '') { - return new Factory.Stream().tcp({ bind_addr: arg }); - } else { - return new Error(`No bidning address for TCP connection`); - } - } else if (target === 'udp') { - const settings = udp(arg); - if (settings instanceof Error) { - return settings; - } else { - return new Factory.Stream().udp(settings); - } - } - return new Error(`Unknown target for streaming: ${target}`); -} +// function getObserveFactory( +// target: string | undefined, +// cwd: string, +// arg: string, +// ): Factory.Stream | Error { +// if (target === 'stdout') { +// if (arg.trim() !== '') { +// return new Factory.Stream().process({ +// command: arg, +// cwd, +// envs: {}, +// }); +// } else { +// return new Error(`Command to spawn cannot be empty`); +// } +// } else if (target === 'serial') { +// const settings = serial(arg); +// if (settings instanceof Error) { +// return settings; +// } else { +// return new Factory.Stream().serial(settings); +// } +// } else if (target === 'tcp') { +// if (arg.trim() !== '') { +// return new Factory.Stream().tcp({ bind_addr: arg }); +// } else { +// return new Error(`No bidning address for TCP connection`); +// } +// } else if (target === 'udp') { +// const settings = udp(arg); +// if (settings instanceof Error) { +// return settings; +// } else { +// return new Factory.Stream().udp(settings); +// } +// } +// return new Error(`Unknown target for streaming: ${target}`); +// } export class Action extends CLIAction { - protected factories: Factory.Stream[] = []; + // protected factories: Factory.Stream[] = []; protected error: Error[] = []; public argument(target: string | undefined, cwd: string, arg: string): string { - const factory = getObserveFactory(target, cwd, arg); - if (factory instanceof Error) { - this.error.push(factory); - return ''; - } - this.factories.push(factory); + // const factory = getObserveFactory(target, cwd, arg); + // if (factory instanceof Error) { + // this.error.push(factory); + // return ''; + // } + // this.factories.push(factory); return arg; } @@ -202,26 +200,27 @@ export class Action extends CLIAction { if (!this.defined()) { return Promise.resolve(); } - return new Promise((resolve, _reject) => { - Requests.IpcRequest.send( - Requests.Cli.Observe.Response, - new Requests.Cli.Observe.Request({ - observe: this.factories.map((factory) => - factory.protocol(cli.state().parser()).get().sterilized(), - ), - }), - ) - .then((response) => { - if (response.session === undefined) { - return; - } - cli.state().sessions([response.session]); - }) - .catch((err: Error) => { - cli.log().error(`Fail apply stream action: ${err.message}`); - }) - .finally(resolve); - }); + return Promise.reject(new Error(`Not implemented!`)); + // return new Promise((resolve, _reject) => { + // Requests.IpcRequest.send( + // Requests.Cli.Observe.Response, + // new Requests.Cli.Observe.Request({ + // observe: this.factories.map((factory) => + // factory.protocol(cli.state().parser()).get().sterilized(), + // ), + // }), + // ) + // .then((response) => { + // if (response.session === undefined) { + // return; + // } + // cli.state().sessions([response.session]); + // }) + // .catch((err: Error) => { + // cli.log().error(`Fail apply stream action: ${err.message}`); + // }) + // .finally(resolve); + // }); } public type(): Type { @@ -229,6 +228,7 @@ export class Action extends CLIAction { } public defined(): boolean { - return this.factories.length > 0; + return false; + // return this.factories.length > 0; } } diff --git a/application/holder/src/service/components.ts b/application/holder/src/service/components.ts new file mode 100644 index 000000000..da8c1e3d2 --- /dev/null +++ b/application/holder/src/service/components.ts @@ -0,0 +1,219 @@ +import { + SetupService, + Interface, + Implementation, + register, + DependOn, +} from 'platform/entity/service'; +import { services } from '@register/services'; +import { electron } from '@service/electron'; +import { Components } from 'rustcore'; +import { Mutable } from 'platform/types/unity/mutable'; + +import * as RequestHandlers from './components/index'; +import * as Requests from 'platform/ipc/request'; +import * as Events from 'platform/ipc/event'; + +import { + LoadingCancelledEvent, + LoadingDoneEvent, + LoadingErrorEvent, + LoadingErrorsEvent, +} from 'platform/types/components'; + +@DependOn(electron) +@SetupService(services['components']) +export class Service extends Implementation { + public readonly components!: Components; + + public override async ready(): Promise { + (this as Mutable).components = await Components.create(); + this.register( + electron + .ipc() + .respondent( + this.getName(), + Requests.Components.Abort.Request, + RequestHandlers.Abort.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Components.GetOptions.Request, + RequestHandlers.GetOptions.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Components.GetIdent.Request, + RequestHandlers.GetIdent.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Components.GetOutputRender.Request, + RequestHandlers.GetOutputRender.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Components.IsSdeSupported.Request, + RequestHandlers.IsSdeSupported.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Components.GetCompatibleSetup.Request, + RequestHandlers.GetCompatibleSetup.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Components.GetDefaultOptions.Request, + RequestHandlers.GetDefaultOptions.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Components.GetParsers.Request, + RequestHandlers.GetParsers.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Components.GetSources.Request, + RequestHandlers.GetSources.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Components.Validate.Request, + RequestHandlers.Validate.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Plugins.ListInstalled.Request, + RequestHandlers.Plugins.ListInstalled.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Plugins.ListInvalid.Request, + RequestHandlers.Plugins.ListInvalid.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Plugins.ListInstalledPaths.Request, + RequestHandlers.Plugins.ListInstalledPaths.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Plugins.ListInvalidPaths.Request, + RequestHandlers.Plugins.ListInvalidPaths.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Plugins.InstalledPluginInfo.Request, + RequestHandlers.Plugins.InstalledPluginInfo.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Plugins.InvalidPluginInfo.Request, + RequestHandlers.Plugins.InvalidPluginInfo.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Plugins.PluginRunData.Request, + RequestHandlers.Plugins.PluginRunData.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Plugins.Reload.Request, + RequestHandlers.Plugins.Relaod.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Plugins.AddPlugin.Request, + RequestHandlers.Plugins.AddPlugin.handler, + ), + electron + .ipc() + .respondent( + this.getName(), + Requests.Plugins.RemovePlugin.Request, + RequestHandlers.Plugins.RemovePlugin.handler, + ), + this.components.getEvents().LoadingDone.subscribe((event: LoadingDoneEvent) => { + Events.IpcEvent.emit( + new Events.Components.LoadingDone.Event({ + event, + }), + ); + }), + this.components.getEvents().LoadingError.subscribe((event: LoadingErrorEvent) => { + Events.IpcEvent.emit( + new Events.Components.LoadingError.Event({ + event, + }), + ); + }), + this.components.getEvents().LoadingErrors.subscribe((event: LoadingErrorsEvent) => { + Events.IpcEvent.emit( + new Events.Components.LoadingErrors.Event({ + event, + }), + ); + }), + this.components + .getEvents() + .LoadingCancelled.subscribe((event: LoadingCancelledEvent) => { + Events.IpcEvent.emit( + new Events.Components.LoadingCancelled.Event({ + event, + }), + ); + }), + this.components.getEvents().Destroyed.subscribe(() => { + // Stop getting incoming requests + this.unsubscribe(); + }), + ); + + return Promise.resolve(); + } + + public override destroy(): Promise { + this.unsubscribe(); + return this.components.destroy().catch((err: Error) => { + this.log().error(`Fail to shutdown Components: ${err.message}`); + }); + } +} +export interface Service extends Interface {} +export const components = register(new Service()); diff --git a/application/holder/src/service/components/abort.ts b/application/holder/src/service/components/abort.ts new file mode 100644 index 000000000..767cda052 --- /dev/null +++ b/application/holder/src/service/components/abort.ts @@ -0,0 +1,24 @@ +import { CancelablePromise } from 'platform/env/promise'; +import { Logger } from 'platform/log'; +import { components } from '@service/components'; + +import * as Requests from 'platform/ipc/request'; + +export const handler = Requests.InjectLogger< + Requests.Components.Abort.Request, + CancelablePromise +>( + ( + _log: Logger, + request: Requests.Components.Abort.Request, + ): CancelablePromise => { + return new CancelablePromise((resolve, reject) => { + const error = components.components.abort(request.fields); + if (error instanceof Error) { + reject(error); + } else { + resolve(new Requests.Components.Abort.Response()); + } + }); + }, +); diff --git a/application/holder/src/service/components/get_compatible_setup.ts b/application/holder/src/service/components/get_compatible_setup.ts new file mode 100644 index 000000000..ad0c20bf0 --- /dev/null +++ b/application/holder/src/service/components/get_compatible_setup.ts @@ -0,0 +1,25 @@ +import { CancelablePromise } from 'platform/env/promise'; +import { Logger } from 'platform/log'; +import { components } from '@service/components'; +import { ComponentsList } from 'platform/types/bindings'; + +import * as Requests from 'platform/ipc/request'; + +export const handler = Requests.InjectLogger< + Requests.Components.GetCompatibleSetup.Request, + CancelablePromise +>( + ( + _log: Logger, + request: Requests.Components.GetCompatibleSetup.Request, + ): CancelablePromise => { + return new CancelablePromise((resolve, reject) => { + components.components + .getCompatibleSetup(request.origin) + .then((components: ComponentsList) => { + resolve(new Requests.Components.GetCompatibleSetup.Response({ components })); + }) + .catch(reject); + }); + }, +); diff --git a/application/holder/src/service/components/get_default_options.ts b/application/holder/src/service/components/get_default_options.ts new file mode 100644 index 000000000..25b498e69 --- /dev/null +++ b/application/holder/src/service/components/get_default_options.ts @@ -0,0 +1,25 @@ +import { CancelablePromise } from 'platform/env/promise'; +import { Logger } from 'platform/log'; +import { components } from '@service/components'; +import { FieldList } from 'platform/types/bindings'; + +import * as Requests from 'platform/ipc/request'; + +export const handler = Requests.InjectLogger< + Requests.Components.GetDefaultOptions.Request, + CancelablePromise +>( + ( + _log: Logger, + request: Requests.Components.GetDefaultOptions.Request, + ): CancelablePromise => { + return new CancelablePromise((resolve, reject) => { + components.components + .getDefaultOptions(request.origin, request.uuid) + .then((fields: FieldList) => { + resolve(new Requests.Components.GetDefaultOptions.Response({ fields })); + }) + .catch(reject); + }); + }, +); diff --git a/application/holder/src/service/components/get_ident.ts b/application/holder/src/service/components/get_ident.ts new file mode 100644 index 000000000..b7cbe54aa --- /dev/null +++ b/application/holder/src/service/components/get_ident.ts @@ -0,0 +1,29 @@ +import { CancelablePromise } from 'platform/env/promise'; +import { Logger } from 'platform/log'; +import { components } from '@service/components'; +import { Ident } from 'platform/types/bindings'; + +import * as Requests from 'platform/ipc/request'; + +export const handler = Requests.InjectLogger< + Requests.Components.GetIdent.Request, + CancelablePromise +>( + ( + _log: Logger, + request: Requests.Components.GetIdent.Request, + ): CancelablePromise => { + return new CancelablePromise((resolve, reject) => { + components.components + .getIdent(request.target) + .then((ident: Ident | undefined | null) => { + resolve( + new Requests.Components.GetIdent.Response({ + ident: ident ? ident : undefined, + }), + ); + }) + .catch(reject); + }); + }, +); diff --git a/application/holder/src/service/components/get_options.ts b/application/holder/src/service/components/get_options.ts new file mode 100644 index 000000000..dc67d048c --- /dev/null +++ b/application/holder/src/service/components/get_options.ts @@ -0,0 +1,25 @@ +import { CancelablePromise } from 'platform/env/promise'; +import { Logger } from 'platform/log'; +import { components } from '@service/components'; +import { FieldDesc } from 'platform/types/bindings'; + +import * as Requests from 'platform/ipc/request'; + +export const handler = Requests.InjectLogger< + Requests.Components.GetOptions.Request, + CancelablePromise +>( + ( + _log: Logger, + request: Requests.Components.GetOptions.Request, + ): CancelablePromise => { + return new CancelablePromise((resolve, reject) => { + components.components + .getOptions(request.origin, request.targets) + .then((options: Map) => { + resolve(new Requests.Components.GetOptions.Response({ options })); + }) + .catch(reject); + }); + }, +); diff --git a/application/holder/src/service/components/get_output_render.ts b/application/holder/src/service/components/get_output_render.ts new file mode 100644 index 000000000..5663cad48 --- /dev/null +++ b/application/holder/src/service/components/get_output_render.ts @@ -0,0 +1,25 @@ +import { CancelablePromise } from 'platform/env/promise'; +import { Logger } from 'platform/log'; +import { components } from '@service/components'; +import { FieldDesc, OutputRender } from 'platform/types/bindings'; + +import * as Requests from 'platform/ipc/request'; + +export const handler = Requests.InjectLogger< + Requests.Components.GetOutputRender.Request, + CancelablePromise +>( + ( + _log: Logger, + request: Requests.Components.GetOutputRender.Request, + ): CancelablePromise => { + return new CancelablePromise((resolve, reject) => { + components.components + .getOutputRender(request.uuid) + .then((render: OutputRender | null) => { + resolve(new Requests.Components.GetOutputRender.Response({ render })); + }) + .catch(reject); + }); + }, +); diff --git a/application/holder/src/service/components/get_parsers.ts b/application/holder/src/service/components/get_parsers.ts new file mode 100644 index 000000000..34ad96b8b --- /dev/null +++ b/application/holder/src/service/components/get_parsers.ts @@ -0,0 +1,26 @@ +import { CancelablePromise } from 'platform/env/promise'; +import { Logger } from 'platform/log'; +import { components } from '@service/components'; +import { Ident } from 'platform/types/bindings'; + +import * as Requests from 'platform/ipc/request'; + +export const handler = Requests.InjectLogger< + Requests.Components.GetParsers.Request, + CancelablePromise +>( + ( + _log: Logger, + request: Requests.Components.GetParsers.Request, + ): CancelablePromise => { + return new CancelablePromise((resolve, reject) => { + components.components + .get(request.origin) + .parsers() + .then((list: Ident[]) => { + resolve(new Requests.Components.GetParsers.Response({ list })); + }) + .catch(reject); + }); + }, +); diff --git a/application/holder/src/service/components/get_sources.ts b/application/holder/src/service/components/get_sources.ts new file mode 100644 index 000000000..6157e5432 --- /dev/null +++ b/application/holder/src/service/components/get_sources.ts @@ -0,0 +1,26 @@ +import { CancelablePromise } from 'platform/env/promise'; +import { Logger } from 'platform/log'; +import { components } from '@service/components'; +import { Ident } from 'platform/types/bindings'; + +import * as Requests from 'platform/ipc/request'; + +export const handler = Requests.InjectLogger< + Requests.Components.GetSources.Request, + CancelablePromise +>( + ( + _log: Logger, + request: Requests.Components.GetSources.Request, + ): CancelablePromise => { + return new CancelablePromise((resolve, reject) => { + components.components + .get(request.origin) + .sources() + .then((list: Ident[]) => { + resolve(new Requests.Components.GetSources.Response({ list })); + }) + .catch(reject); + }); + }, +); diff --git a/application/holder/src/service/components/index.ts b/application/holder/src/service/components/index.ts new file mode 100644 index 000000000..c1865e3d5 --- /dev/null +++ b/application/holder/src/service/components/index.ts @@ -0,0 +1,11 @@ +export * as GetSources from './get_sources'; +export * as GetParsers from './get_parsers'; +export * as GetOptions from './get_options'; +export * as GetOutputRender from './get_output_render'; +export * as GetIdent from './get_ident'; +export * as Abort from './abort'; +export * as Validate from './validate'; +export * as Plugins from './plugins'; +export * as IsSdeSupported from './is_sde_supported'; +export * as GetCompatibleSetup from './get_compatible_setup'; +export * as GetDefaultOptions from './get_default_options'; diff --git a/application/holder/src/service/components/is_sde_supported.ts b/application/holder/src/service/components/is_sde_supported.ts new file mode 100644 index 000000000..76257dbfb --- /dev/null +++ b/application/holder/src/service/components/is_sde_supported.ts @@ -0,0 +1,24 @@ +import { CancelablePromise } from 'platform/env/promise'; +import { Logger } from 'platform/log'; +import { components } from '@service/components'; + +import * as Requests from 'platform/ipc/request'; + +export const handler = Requests.InjectLogger< + Requests.Components.IsSdeSupported.Request, + CancelablePromise +>( + ( + _log: Logger, + request: Requests.Components.IsSdeSupported.Request, + ): CancelablePromise => { + return new CancelablePromise((resolve, reject) => { + components.components + .isSdeSupported(request.uuid, request.origin) + .then((support: boolean) => { + resolve(new Requests.Components.IsSdeSupported.Response({ support })); + }) + .catch(reject); + }); + }, +); diff --git a/application/holder/src/service/unbound/plugins/add_plugin.ts b/application/holder/src/service/components/plugins/add_plugin.ts similarity index 89% rename from application/holder/src/service/unbound/plugins/add_plugin.ts rename to application/holder/src/service/components/plugins/add_plugin.ts index 7c75f5d61..b47d7a868 100644 --- a/application/holder/src/service/unbound/plugins/add_plugin.ts +++ b/application/holder/src/service/components/plugins/add_plugin.ts @@ -1,6 +1,6 @@ import { CancelablePromise } from 'platform/env/promise'; import { Logger } from 'platform/log'; -import { unbound } from '@service/unbound'; +import { components } from '@service/components'; import * as Requests from 'platform/ipc/request'; @@ -13,7 +13,7 @@ export const handler = Requests.InjectLogger< request: Requests.Plugins.AddPlugin.Request, ): CancelablePromise => { return new CancelablePromise((reslove, reject) => { - unbound.jobs + components.components .addPlugin(request.pluginPath) .then(() => { reslove(new Requests.Plugins.AddPlugin.Response({})); diff --git a/application/holder/src/service/unbound/plugins/index.ts b/application/holder/src/service/components/plugins/index.ts similarity index 100% rename from application/holder/src/service/unbound/plugins/index.ts rename to application/holder/src/service/components/plugins/index.ts diff --git a/application/holder/src/service/unbound/plugins/installed_plugin_info.ts b/application/holder/src/service/components/plugins/installed_plugin_info.ts similarity index 90% rename from application/holder/src/service/unbound/plugins/installed_plugin_info.ts rename to application/holder/src/service/components/plugins/installed_plugin_info.ts index c5bf5b419..9f91f12dc 100644 --- a/application/holder/src/service/unbound/plugins/installed_plugin_info.ts +++ b/application/holder/src/service/components/plugins/installed_plugin_info.ts @@ -1,6 +1,6 @@ import { CancelablePromise } from 'platform/env/promise'; import { Logger } from 'platform/log'; -import { unbound } from '@service/unbound'; +import { components } from '@service/components'; import * as Requests from 'platform/ipc/request'; @@ -13,7 +13,7 @@ export const handler = Requests.InjectLogger< request: Requests.Plugins.InstalledPluginInfo.Request, ): CancelablePromise => { return new CancelablePromise((reslove, reject) => { - unbound.jobs + components.components .installedPluginsInfo(request.pluginPath) .then((plugin) => { reslove(new Requests.Plugins.InstalledPluginInfo.Response({ plugin })); diff --git a/application/holder/src/service/unbound/plugins/invalid_plugin_info.ts b/application/holder/src/service/components/plugins/invalid_plugin_info.ts similarity index 90% rename from application/holder/src/service/unbound/plugins/invalid_plugin_info.ts rename to application/holder/src/service/components/plugins/invalid_plugin_info.ts index 937b4f972..47ceb1c37 100644 --- a/application/holder/src/service/unbound/plugins/invalid_plugin_info.ts +++ b/application/holder/src/service/components/plugins/invalid_plugin_info.ts @@ -1,6 +1,6 @@ import { CancelablePromise } from 'platform/env/promise'; import { Logger } from 'platform/log'; -import { unbound } from '@service/unbound'; +import { components } from '@service/components'; import * as Requests from 'platform/ipc/request'; @@ -13,7 +13,7 @@ export const handler = Requests.InjectLogger< request: Requests.Plugins.InvalidPluginInfo.Request, ): CancelablePromise => { return new CancelablePromise((reslove, reject) => { - unbound.jobs + components.components .invalidPluginsInfo(request.pluginPath) .then((invalidPlugin) => { reslove(new Requests.Plugins.InvalidPluginInfo.Response({ invalidPlugin })); diff --git a/application/holder/src/service/unbound/plugins/list_installed.ts b/application/holder/src/service/components/plugins/list_installed.ts similarity index 90% rename from application/holder/src/service/unbound/plugins/list_installed.ts rename to application/holder/src/service/components/plugins/list_installed.ts index 0b376cf10..9c7ab85df 100644 --- a/application/holder/src/service/unbound/plugins/list_installed.ts +++ b/application/holder/src/service/components/plugins/list_installed.ts @@ -1,6 +1,6 @@ import { CancelablePromise } from 'platform/env/promise'; import { Logger } from 'platform/log'; -import { unbound } from '@service/unbound'; +import { components } from '@service/components'; import * as Requests from 'platform/ipc/request'; @@ -13,7 +13,7 @@ export const handler = Requests.InjectLogger< _request: Requests.Plugins.ListInstalled.Request, ): CancelablePromise => { return new CancelablePromise((reslove, reject) => { - unbound.jobs + components.components .installedPluginsList() .then((plugins) => { reslove(new Requests.Plugins.ListInstalled.Response({ plugins })); diff --git a/application/holder/src/service/unbound/plugins/list_installed_paths.ts b/application/holder/src/service/components/plugins/list_installed_paths.ts similarity index 90% rename from application/holder/src/service/unbound/plugins/list_installed_paths.ts rename to application/holder/src/service/components/plugins/list_installed_paths.ts index 07ca8639d..0edf3689d 100644 --- a/application/holder/src/service/unbound/plugins/list_installed_paths.ts +++ b/application/holder/src/service/components/plugins/list_installed_paths.ts @@ -1,6 +1,6 @@ import { CancelablePromise } from 'platform/env/promise'; import { Logger } from 'platform/log'; -import { unbound } from '@service/unbound'; +import { components } from '@service/components'; import * as Requests from 'platform/ipc/request'; @@ -13,7 +13,7 @@ export const handler = Requests.InjectLogger< _request: Requests.Plugins.ListInstalledPaths.Request, ): CancelablePromise => { return new CancelablePromise((reslove, reject) => { - unbound.jobs + components.components .installedPluginsPaths() .then((paths) => { reslove(new Requests.Plugins.ListInstalledPaths.Response({ paths })); diff --git a/application/holder/src/service/unbound/plugins/list_invalid.ts b/application/holder/src/service/components/plugins/list_invalid.ts similarity index 90% rename from application/holder/src/service/unbound/plugins/list_invalid.ts rename to application/holder/src/service/components/plugins/list_invalid.ts index b48945c75..84cedcece 100644 --- a/application/holder/src/service/unbound/plugins/list_invalid.ts +++ b/application/holder/src/service/components/plugins/list_invalid.ts @@ -1,6 +1,6 @@ import { CancelablePromise } from 'platform/env/promise'; import { Logger } from 'platform/log'; -import { unbound } from '@service/unbound'; +import { components } from '@service/components'; import * as Requests from 'platform/ipc/request'; @@ -13,7 +13,7 @@ export const handler = Requests.InjectLogger< _request: Requests.Plugins.ListInvalid.Request, ): CancelablePromise => { return new CancelablePromise((reslove, reject) => { - unbound.jobs + components.components .invalidPluginsList() .then((invalidPlugins) => { reslove(new Requests.Plugins.ListInvalid.Response({ invalidPlugins })); diff --git a/application/holder/src/service/unbound/plugins/list_invalid_paths.ts b/application/holder/src/service/components/plugins/list_invalid_paths.ts similarity index 90% rename from application/holder/src/service/unbound/plugins/list_invalid_paths.ts rename to application/holder/src/service/components/plugins/list_invalid_paths.ts index 33c91c291..8e179cdd6 100644 --- a/application/holder/src/service/unbound/plugins/list_invalid_paths.ts +++ b/application/holder/src/service/components/plugins/list_invalid_paths.ts @@ -1,6 +1,6 @@ import { CancelablePromise } from 'platform/env/promise'; import { Logger } from 'platform/log'; -import { unbound } from '@service/unbound'; +import { components } from '@service/components'; import * as Requests from 'platform/ipc/request'; @@ -13,7 +13,7 @@ export const handler = Requests.InjectLogger< _request: Requests.Plugins.ListInvalidPaths.Request, ): CancelablePromise => { return new CancelablePromise((reslove, reject) => { - unbound.jobs + components.components .invalidPluginsPaths() .then((paths) => { reslove(new Requests.Plugins.ListInvalidPaths.Response({ paths })); diff --git a/application/holder/src/service/unbound/plugins/reload.ts b/application/holder/src/service/components/plugins/reload.ts similarity index 89% rename from application/holder/src/service/unbound/plugins/reload.ts rename to application/holder/src/service/components/plugins/reload.ts index efcc7a2a0..e212c7426 100644 --- a/application/holder/src/service/unbound/plugins/reload.ts +++ b/application/holder/src/service/components/plugins/reload.ts @@ -1,6 +1,6 @@ import { CancelablePromise } from 'platform/env/promise'; import { Logger } from 'platform/log'; -import { unbound } from '@service/unbound'; +import { components } from '@service/components'; import * as Requests from 'platform/ipc/request'; @@ -13,7 +13,7 @@ export const handler = Requests.InjectLogger< _request: Requests.Plugins.Reload.Request, ): CancelablePromise => { return new CancelablePromise((resolve, reject) => { - unbound.jobs + components.components .reloadPlugins() .then(() => { resolve(new Requests.Plugins.Reload.Response({})); diff --git a/application/holder/src/service/unbound/plugins/remove_plugin.ts b/application/holder/src/service/components/plugins/remove_plugin.ts similarity index 89% rename from application/holder/src/service/unbound/plugins/remove_plugin.ts rename to application/holder/src/service/components/plugins/remove_plugin.ts index b3893d488..a9faa96c0 100644 --- a/application/holder/src/service/unbound/plugins/remove_plugin.ts +++ b/application/holder/src/service/components/plugins/remove_plugin.ts @@ -1,6 +1,6 @@ import { CancelablePromise } from 'platform/env/promise'; import { Logger } from 'platform/log'; -import { unbound } from '@service/unbound'; +import { components } from '@service/components'; import * as Requests from 'platform/ipc/request'; @@ -13,7 +13,7 @@ export const handler = Requests.InjectLogger< request: Requests.Plugins.RemovePlugin.Request, ): CancelablePromise => { return new CancelablePromise((reslove, reject) => { - unbound.jobs + components.components .removePlugin(request.pluginPath) .then(() => { reslove(new Requests.Plugins.RemovePlugin.Response({})); diff --git a/application/holder/src/service/unbound/plugins/rundata.ts b/application/holder/src/service/components/plugins/rundata.ts similarity index 90% rename from application/holder/src/service/unbound/plugins/rundata.ts rename to application/holder/src/service/components/plugins/rundata.ts index a8d9ea88e..4b6288533 100644 --- a/application/holder/src/service/unbound/plugins/rundata.ts +++ b/application/holder/src/service/components/plugins/rundata.ts @@ -1,6 +1,6 @@ import { CancelablePromise } from 'platform/env/promise'; import { Logger } from 'platform/log'; -import { unbound } from '@service/unbound'; +import { components } from '@service/components'; import * as Requests from 'platform/ipc/request'; @@ -13,7 +13,7 @@ export const handler = Requests.InjectLogger< request: Requests.Plugins.PluginRunData.Request, ): CancelablePromise => { return new CancelablePromise((reslove, reject) => { - unbound.jobs + components.components .getPluginRunData(request.pluginPath) .then((data) => { reslove(new Requests.Plugins.PluginRunData.Response({ data })); diff --git a/application/holder/src/service/components/validate.ts b/application/holder/src/service/components/validate.ts new file mode 100644 index 000000000..2e418e38c --- /dev/null +++ b/application/holder/src/service/components/validate.ts @@ -0,0 +1,24 @@ +import { CancelablePromise } from 'platform/env/promise'; +import { Logger } from 'platform/log'; +import { components } from '@service/components'; + +import * as Requests from 'platform/ipc/request'; + +export const handler = Requests.InjectLogger< + Requests.Components.Validate.Request, + CancelablePromise +>( + ( + _log: Logger, + request: Requests.Components.Validate.Request, + ): CancelablePromise => { + return new CancelablePromise((resolve, reject) => { + components.components + .validate(request.origin, request.target, request.fields) + .then((errors: Map) => { + resolve(new Requests.Components.Validate.Response({ errors })); + }) + .catch(reject); + }); + }, +); diff --git a/application/holder/src/service/jobs/job.ts b/application/holder/src/service/jobs/job.ts index c071b048b..4a22ccbbb 100644 --- a/application/holder/src/service/jobs/job.ts +++ b/application/holder/src/service/jobs/job.ts @@ -38,7 +38,7 @@ export class Job { this.session = validator.getAsNotEmptyStringOrAsUndefined(job, 'session'); this.name = validator.getAsNotEmptyStringOrAsUndefined(job, 'name'); this.desc = validator.getAsNotEmptyStringOrAsUndefined(job, 'desc'); - this.icon = validator.getAsNotEmptyStringOrAsUndefined(job, 'icon'); + this.icon = validator.getAsStringOrAsUndefined(job, 'icon'); this.progress = validator.getAsValidNumber(job, 'progress', { defaults: 0, max: 100, @@ -122,7 +122,12 @@ export class Job { name: this.name, desc: this.desc, progress: this.progress, - icon: this.icon, + icon: + this.icon !== undefined + ? this.icon.trim() === '' + ? undefined + : this.icon + : undefined, }), ); this._done(this); diff --git a/application/holder/src/service/menu.ts b/application/holder/src/service/menu.ts index 41f3bcf13..f744b5b59 100644 --- a/application/holder/src/service/menu.ts +++ b/application/holder/src/service/menu.ts @@ -10,11 +10,10 @@ import { cli } from '@service/cli'; import { Menu, MenuItem } from 'electron'; import { notifications } from '@service/notifications'; import { unique } from 'platform/env/sequence'; -import { FileType } from 'platform/types/observe/types/file'; +import { FileType } from 'platform/types/files'; import { ChipmunkGlobal } from '@register/global'; import * as Actions from './actions'; -import * as $ from 'platform/types/observe'; declare const global: ChipmunkGlobal; @@ -455,89 +454,89 @@ export class Service extends Implementation { }, ], }, - { - label: 'Connections', - submenu: [ - { - label: 'DLT on UDP', - click: async () => { - Actions.stream( - $.Parser.Protocol.Dlt, - $.Origin.Stream.Stream.Source.UDP, - ).catch((err: Error) => { - this.log().error(`Fail call action Stream: ${err.message}`); - }); - }, - }, - { - label: 'DLT on TCP', - click: async () => { - Actions.stream( - $.Parser.Protocol.Dlt, - $.Origin.Stream.Stream.Source.TCP, - ).catch((err: Error) => { - this.log().error(`Fail call action Stream: ${err.message}`); - }); - }, - }, - { - label: 'DLT on Serial Port', - click: async () => { - Actions.stream( - $.Parser.Protocol.Dlt, - $.Origin.Stream.Stream.Source.Serial, - ).catch((err: Error) => { - this.log().error(`Fail call action Stream: ${err.message}`); - }); - }, - }, - { type: 'separator' }, - { - label: 'Plain text Serial Port', - click: async () => { - Actions.stream( - $.Parser.Protocol.Text, - $.Origin.Stream.Stream.Source.Serial, - ).catch((err: Error) => { - this.log().error(`Fail call action Stream: ${err.message}`); - }); - }, - }, - { type: 'separator' }, - { - label: 'Select Source for Plain text', - click: async () => { - Actions.stream($.Parser.Protocol.Text).catch((err: Error) => { - this.log().error(`Fail call action Stream: ${err.message}`); - }); - }, - }, - { - label: 'Select Source for DLT', - click: async () => { - Actions.stream($.Parser.Protocol.Dlt).catch((err: Error) => { - this.log().error(`Fail call action Stream: ${err.message}`); - }); - }, - }, - ], - }, - { - label: 'Terminal', - submenu: [ - { - label: 'Execute command', - click: async () => { - Actions.stream( - $.Parser.Protocol.Text, - $.Origin.Stream.Stream.Source.Process, - ).catch((err: Error) => { - this.log().error(`Fail call action Stream: ${err.message}`); - }); - }, - }, - ], - }, + // { + // label: 'Connections', + // submenu: [ + // { + // label: 'DLT on UDP', + // click: async () => { + // Actions.stream( + // $.Parser.Protocol.Dlt, + // $.Origin.Stream.Stream.Source.UDP, + // ).catch((err: Error) => { + // this.log().error(`Fail call action Stream: ${err.message}`); + // }); + // }, + // }, + // { + // label: 'DLT on TCP', + // click: async () => { + // Actions.stream( + // $.Parser.Protocol.Dlt, + // $.Origin.Stream.Stream.Source.TCP, + // ).catch((err: Error) => { + // this.log().error(`Fail call action Stream: ${err.message}`); + // }); + // }, + // }, + // { + // label: 'DLT on Serial Port', + // click: async () => { + // Actions.stream( + // $.Parser.Protocol.Dlt, + // $.Origin.Stream.Stream.Source.Serial, + // ).catch((err: Error) => { + // this.log().error(`Fail call action Stream: ${err.message}`); + // }); + // }, + // }, + // { type: 'separator' }, + // { + // label: 'Plain text Serial Port', + // click: async () => { + // Actions.stream( + // $.Parser.Protocol.Text, + // $.Origin.Stream.Stream.Source.Serial, + // ).catch((err: Error) => { + // this.log().error(`Fail call action Stream: ${err.message}`); + // }); + // }, + // }, + // { type: 'separator' }, + // { + // label: 'Select Source for Plain text', + // click: async () => { + // Actions.stream($.Parser.Protocol.Text).catch((err: Error) => { + // this.log().error(`Fail call action Stream: ${err.message}`); + // }); + // }, + // }, + // { + // label: 'Select Source for DLT', + // click: async () => { + // Actions.stream($.Parser.Protocol.Dlt).catch((err: Error) => { + // this.log().error(`Fail call action Stream: ${err.message}`); + // }); + // }, + // }, + // ], + // }, + // { + // label: 'Terminal', + // submenu: [ + // { + // label: 'Execute command', + // click: async () => { + // Actions.stream( + // $.Parser.Protocol.Text, + // $.Origin.Stream.Stream.Source.Process, + // ).catch((err: Error) => { + // this.log().error(`Fail call action Stream: ${err.message}`); + // }); + // }, + // }, + // ], + // }, { label: 'Edit', submenu: [ diff --git a/application/holder/src/service/sessions.ts b/application/holder/src/service/sessions.ts index 473ff09e1..ca78947de 100644 --- a/application/holder/src/service/sessions.ts +++ b/application/holder/src/service/sessions.ts @@ -7,6 +7,7 @@ import { } from 'platform/entity/service'; import { electron } from '@service/electron'; import { jobs } from '@service/jobs'; +import { components } from '@service/components'; import { services } from '@register/services'; import { Session } from 'rustcore'; import { Active } from './sessions/active'; @@ -19,6 +20,7 @@ import * as Requests from 'platform/ipc/request'; export { Jobs } from './sessions/holder'; @DependOn(jobs) +@DependOn(components) @DependOn(electron) @SetupService(services['sessions']) export class Service extends Implementation { diff --git a/application/holder/src/service/sessions/holder.ts b/application/holder/src/service/sessions/holder.ts index 6734ca811..547b4f351 100644 --- a/application/holder/src/service/sessions/holder.ts +++ b/application/holder/src/service/sessions/holder.ts @@ -5,10 +5,10 @@ import { scope } from 'platform/env/scope'; import { Logger } from 'platform/log'; import { jobs } from '@service/jobs'; import { ICancelablePromise } from 'platform/env/promise'; -import { $ } from 'rustcore'; +import { SessionSetup } from 'platform/types/bindings'; import * as Events from 'platform/ipc/event'; -import * as path from 'path'; +import { IJob } from '@service/jobs/job'; export enum Jobs { search = 'search', @@ -20,11 +20,11 @@ export class Holder { public readonly subscriber: Subscriber; protected readonly jobs: Map = new Map(); protected readonly observing: { - active: Map; - finished: Map; + active: Map; + finished: Set; } = { active: new Map(), - finished: new Map(), + finished: new Set(), }; protected readonly logger: Logger; protected shutdown = false; @@ -69,43 +69,35 @@ export class Holder { } public observe(): { - start(observe: $.IObserve): Promise; + start(setup: SessionSetup): Promise; cancel(uuid: string): Promise; - list(): { [key: string]: string }; + list(): string[]; } { return { - start: (cfg: $.IObserve): Promise => { - const observe = new $.Observe(cfg); + start: (setup: SessionSetup): Promise => { if (this.shutdown) { return Promise.reject(new Error(`Session is closing`)); } - let jobDesc = observe.origin.asJob(); - if (jobDesc instanceof Error) { - this.logger.error(`Fail to get job description: ${jobDesc.message}`); - jobDesc = { - name: 'unknown', - desc: 'unknown', - icon: undefined, - }; - } + const description = this.getDescription(setup); const job = jobs .create({ session: this.session.getUUID(), - name: jobDesc.name, - desc: jobDesc.desc, - icon: jobDesc.icon, + name: description.title, + desc: description.desctiption, + // TODO: probably we should refuse from icons + icon: '', }) .start(); return new Promise((resolve, reject) => { const observer = this.session .getStream() - .observe(cfg) + .observe(setup) .on('confirmed', () => { Events.IpcEvent.emit( new Events.Observe.Started.Event({ session: this.session.getUUID(), operation: observer.uuid(), - source: observe.json().to(), + options: setup, }), ); }) @@ -119,85 +111,75 @@ export class Holder { .finally(() => { job.done(); this.observing.active.delete(observer.uuid()); - this.observing.finished.set(observer.uuid(), observe); + this.observing.finished.add(observer.uuid()); Events.IpcEvent.emit( new Events.Observe.Finished.Event({ session: this.session.getUUID(), operation: observer.uuid(), - source: observe.json().to(), + options: setup, }), ); }); - this.observing.active.set(observer.uuid(), { source: observe, observer }); + this.observing.active.set(observer.uuid(), observer); }); }, cancel: (uuid: string): Promise => { - const operation = this.observing.active.get(uuid); - if (operation === undefined) { - return Promise.reject(new Error(`AAA Operation isn't found`)); + const observer = this.observing.active.get(uuid); + if (observer === undefined) { + return Promise.reject(new Error(`Operation isn't found`)); } return new Promise((resolve) => { - operation.observer + observer .finally(() => { resolve(); }) .abort(); }); }, - list: (): { [key: string]: string } => { - const list: { [key: string]: string } = {}; - this.observing.active.forEach((operation, uuid) => { - list[uuid] = operation.source.json().to(); - }); - return list; + list: (): string[] => { + return Array.from(this.observing.active.keys()); }, }; } - public getFileExt(): string | Error { - const all = [ - Array.from(this.observing.active.values()).map((o) => o.source), - Array.from(this.observing.finished.values()), - ].flat(); - const files: Array = all - .map((o) => o.origin.files()) - .filter((f) => f !== undefined) - .flat(); - if (files.filter((f) => f === undefined).length > 0) { - return new Error(`Streams arn't supported yet`); - } - const parsers: $.Parser.Protocol[] = []; - all.forEach((observe) => { - if (parsers.includes(observe.parser.alias())) { - return; - } - parsers.push(observe.parser.alias()); - }); - if (parsers.length > 1) { - return new Error(`Multiple parsers are used`); - } else if (parsers.length === 0) { - return new Error(`No parsers has been found`); - } - const exts: string[] = files - .map((f) => path.extname(f as string)) - .filter((ex) => ex.trim() !== ''); - switch (parsers[0]) { - case $.Parser.Protocol.Text: - return `.txt`; - case $.Parser.Protocol.Plugin: - return exts.length === 0 ? '.plg' : exts[0]; - case $.Parser.Protocol.Dlt: - case $.Parser.Protocol.SomeIp: - if (files.length === 0) { - return new Error( - `No assigned files are found. Exporting from stream into new session arn't supported`, - ); - } - return exts.length === 0 ? '' : exts[0]; - } + public getExportedFileExt(): string | Error { + // TODO (Not implemented. API required) + // Since in the new paradigm the client has no knowledge of which specific + // parser/source is being used, implementing this functionality requires an additional API. + // Specifically, rustcore should return the appropriate file extension for exporting data, + // based on the parser UUID. + // + // For example, if the active session is DLT over UDP, the file extension should be *.dlt. + // If it’s SomeIP over TCP, the expected extension would likely be *.pcap. + // + // The client cannot determine this reliably on its own, which is why an API is needed + // to associate the parser with the correct logic for exporting data. + return new Error(`getExportedFileExt requires API. Not implemented`); } public isShutdowning(): boolean { return this.shutdown; } + + protected getDescription(setup: SessionSetup): { + title: string; + desctiption: string | undefined; + } { + if (setup.origin === 'Source') { + // TODO: Check idents + return { + title: 'Custom Source', + desctiption: `Data comes from selected source provider`, + }; + } else if ((setup.origin as { File: string }).File) { + return { title: `Selected File`, desctiption: (setup.origin as { File: string }).File }; + } else if ((setup.origin as { Files: string[] }).Files) { + return { + title: `Collection of Files`, + desctiption: `${(setup.origin as { Files: string[] }).Files.length} files`, + }; + } else { + return { title: 'Unknown', desctiption: undefined }; + } + } } diff --git a/application/holder/src/service/sessions/requests/observe/list.ts b/application/holder/src/service/sessions/requests/observe/list.ts index d40a64f48..541eb37a5 100644 --- a/application/holder/src/service/sessions/requests/observe/list.ts +++ b/application/holder/src/service/sessions/requests/observe/list.ts @@ -20,7 +20,7 @@ export const handler = Requests.InjectLogger< resolve( new Requests.Observe.List.Response({ session: stored.session.getUUID(), - sources: stored.observe().list(), + operations: stored.observe().list(), }), ); }); diff --git a/application/holder/src/service/sessions/requests/observe/start.ts b/application/holder/src/service/sessions/requests/observe/start.ts index b84420d54..31449f4f2 100644 --- a/application/holder/src/service/sessions/requests/observe/start.ts +++ b/application/holder/src/service/sessions/requests/observe/start.ts @@ -19,11 +19,12 @@ export const handler = Requests.InjectLogger< } stored .observe() - .start(request.observe) - .then(() => { + .start(request.options) + .then((uuid: string) => { resolve( new Requests.Observe.Start.Response({ session: stored.session.getUUID(), + uuid, }), ); }) @@ -31,6 +32,7 @@ export const handler = Requests.InjectLogger< resolve( new Requests.Observe.Start.Response({ session: stored.session.getUUID(), + uuid: undefined, error: err.message, }), ); diff --git a/application/holder/src/service/sessions/requests/session/create.ts b/application/holder/src/service/sessions/requests/session/create.ts index 3d600b23c..0040765a5 100644 --- a/application/holder/src/service/sessions/requests/session/create.ts +++ b/application/holder/src/service/sessions/requests/session/create.ts @@ -105,6 +105,20 @@ export const handler = Requests.InjectLogger< ); }), ); + subscriber.register( + session.getEvents().SessionDescriptor.subscribe((event) => { + if (!sessions.exists(uuid)) { + return; + } + Events.IpcEvent.emit( + new Events.Stream.SessionDescriptor.Event({ + descriptor: event.desc, + operation: event.uuid, + session: uuid, + }), + ); + }), + ); subscriber.register( session.getEvents().FileRead.subscribe(() => { if (!sessions.exists(uuid)) { diff --git a/application/holder/src/service/sessions/requests/session/export.raw.ts b/application/holder/src/service/sessions/requests/session/export.raw.ts index 022a73be8..846a88c62 100644 --- a/application/holder/src/service/sessions/requests/session/export.raw.ts +++ b/application/holder/src/service/sessions/requests/session/export.raw.ts @@ -24,7 +24,7 @@ export const handler = Requests.InjectLogger< if (request.dest !== undefined) { return request.dest; } else { - const ext = stored.getFileExt(); + const ext = stored.getExportedFileExt(); if (ext instanceof Error) { return ext; } diff --git a/application/holder/src/service/sessions/requests/session/export.ts b/application/holder/src/service/sessions/requests/session/export.ts index 0500fa0c2..d3d2a937e 100644 --- a/application/holder/src/service/sessions/requests/session/export.ts +++ b/application/holder/src/service/sessions/requests/session/export.ts @@ -24,7 +24,7 @@ export const handler = Requests.InjectLogger< if (request.dest !== undefined) { return request.dest; } else { - const ext = stored.getFileExt(); + const ext = stored.getExportedFileExt(); if (ext instanceof Error) { return ext; } diff --git a/application/holder/src/service/unbound.ts b/application/holder/src/service/unbound.ts index f101b40db..789c52feb 100644 --- a/application/holder/src/service/unbound.ts +++ b/application/holder/src/service/unbound.ts @@ -20,15 +20,6 @@ export class Service extends Implementation { public override async ready(): Promise { (this as Mutable).jobs = await Jobs.create(); - this.register( - electron - .ipc() - .respondent( - this.getName(), - Requests.Dlt.Stat.Request, - RequestHandlers.Dlt.Stat.handler, - ), - ); this.register( electron .ipc() @@ -83,96 +74,6 @@ export class Service extends Implementation { RequestHandlers.Serial.Ports.handler, ), ); - this.register( - electron - .ipc() - .respondent( - this.getName(), - Requests.Plugins.ListInstalled.Request, - RequestHandlers.Plugins.ListInstalled.handler, - ), - ); - this.register( - electron - .ipc() - .respondent( - this.getName(), - Requests.Plugins.ListInvalid.Request, - RequestHandlers.Plugins.ListInvalid.handler, - ), - ); - this.register( - electron - .ipc() - .respondent( - this.getName(), - Requests.Plugins.ListInstalledPaths.Request, - RequestHandlers.Plugins.ListInstalledPaths.handler, - ), - ); - this.register( - electron - .ipc() - .respondent( - this.getName(), - Requests.Plugins.ListInvalidPaths.Request, - RequestHandlers.Plugins.ListInvalidPaths.handler, - ), - ); - this.register( - electron - .ipc() - .respondent( - this.getName(), - Requests.Plugins.InstalledPluginInfo.Request, - RequestHandlers.Plugins.InstalledPluginInfo.handler, - ), - ); - this.register( - electron - .ipc() - .respondent( - this.getName(), - Requests.Plugins.InvalidPluginInfo.Request, - RequestHandlers.Plugins.InvalidPluginInfo.handler, - ), - ); - this.register( - electron - .ipc() - .respondent( - this.getName(), - Requests.Plugins.PluginRunData.Request, - RequestHandlers.Plugins.PluginRunData.handler, - ), - ); - this.register( - electron - .ipc() - .respondent( - this.getName(), - Requests.Plugins.Reload.Request, - RequestHandlers.Plugins.Relaod.handler, - ), - ); - this.register( - electron - .ipc() - .respondent( - this.getName(), - Requests.Plugins.AddPlugin.Request, - RequestHandlers.Plugins.AddPlugin.handler, - ), - ); - this.register( - electron - .ipc() - .respondent( - this.getName(), - Requests.Plugins.RemovePlugin.Request, - RequestHandlers.Plugins.RemovePlugin.handler, - ), - ); return Promise.resolve(); } diff --git a/application/holder/src/service/unbound/dlt/index.ts b/application/holder/src/service/unbound/dlt/index.ts deleted file mode 100644 index 8d9feda93..000000000 --- a/application/holder/src/service/unbound/dlt/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as Stat from './stat'; diff --git a/application/holder/src/service/unbound/dlt/stat.ts b/application/holder/src/service/unbound/dlt/stat.ts deleted file mode 100644 index a87d7b20f..000000000 --- a/application/holder/src/service/unbound/dlt/stat.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { CancelablePromise } from 'platform/env/promise'; -import { Logger } from 'platform/log'; -import { jobs } from '@service/jobs'; -import { unbound } from '@service/unbound'; -import { DltStatisticInfo } from 'platform/types/bindings'; - -import * as Requests from 'platform/ipc/request'; - -export const handler = Requests.InjectLogger< - Requests.Dlt.Stat.Request, - CancelablePromise ->( - ( - _log: Logger, - request: Requests.Dlt.Stat.Request, - ): CancelablePromise => { - return new CancelablePromise((resolve, reject) => { - const scanning = jobs - .create({ - name: 'scanning dlt', - desc: - request.files.length === 1 - ? `file: ${request.files[0]}` - : `${request.files.length} for files`, - }) - .start(); - unbound.jobs - .getDltStats(request.files) - .then((stat: DltStatisticInfo) => { - resolve( - new Requests.Dlt.Stat.Response({ - stat, - }), - ); - }) - .catch(reject) - .finally(() => { - scanning.done(); - }); - }); - }, -); diff --git a/application/holder/src/service/unbound/index.ts b/application/holder/src/service/unbound/index.ts index 6d30af468..52027b11d 100644 --- a/application/holder/src/service/unbound/index.ts +++ b/application/holder/src/service/unbound/index.ts @@ -1,6 +1,3 @@ -export * as Dlt from './dlt'; export * as Os from './os'; export * as File from './file'; export * as Serial from './serial'; -export * as Someip from './someip'; -export * as Plugins from './plugins'; diff --git a/application/holder/src/service/unbound/someip/index.ts b/application/holder/src/service/unbound/someip/index.ts deleted file mode 100644 index 48f2d93fd..000000000 --- a/application/holder/src/service/unbound/someip/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as Statistic from './statistic'; diff --git a/application/holder/src/service/unbound/someip/statistic.ts b/application/holder/src/service/unbound/someip/statistic.ts deleted file mode 100644 index 1636b4aaa..000000000 --- a/application/holder/src/service/unbound/someip/statistic.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { CancelablePromise } from 'platform/env/promise'; -import { Logger } from 'platform/log'; -import { jobs } from '@service/jobs'; -import { unbound } from '@service/unbound'; -import { SomeipStatistic } from 'platform/types/observe/parser/someip'; - -import * as Requests from 'platform/ipc/request'; - -export const handler = Requests.InjectLogger< - Requests.Dlt.Stat.Request, - CancelablePromise ->( - ( - _log: Logger, - request: Requests.Dlt.Stat.Request, - ): CancelablePromise => { - return new CancelablePromise((resolve, reject) => { - const scanning = jobs - .create({ - name: 'scanning dlt', - desc: - request.files.length === 1 - ? `file: ${request.files[0]}` - : `${request.files.length} for files`, - }) - .start(); - unbound.jobs - .getSomeipStatistic(request.files) - .then((statistic: SomeipStatistic) => { - resolve( - new Requests.Someip.Statistic.Response({ - statistic, - }), - ); - }) - .catch(reject) - .finally(() => { - scanning.done(); - }); - }); - }, -); diff --git a/application/platform/env/obj.ts b/application/platform/env/obj.ts index e4cb4bb4c..7d008c24e 100644 --- a/application/platform/env/obj.ts +++ b/application/platform/env/obj.ts @@ -203,6 +203,13 @@ export function getAsNotEmptyStringOrAsUndefined(src: any, key: string): string } return src[key]; } +export function getAsStringOrAsUndefined(src: any, key: string): string | undefined { + if (typeof src[key] === 'string' && src[key].trim() !== '') { + return src[key]; + } else { + return undefined; + } +} export function getAsBool(src: any, key: string, defaults?: boolean): boolean { if (typeof src[key] !== 'boolean') { diff --git a/application/platform/env/subscription.ts b/application/platform/env/subscription.ts index 9a6eaa118..0e830a3e3 100644 --- a/application/platform/env/subscription.ts +++ b/application/platform/env/subscription.ts @@ -44,7 +44,7 @@ export function validateEventDesc(desc: IEventDesc, target: any): Error | undefi )}`, ); } - if (typeof desc.self !== 'string') { + if (typeof desc.self === 'object') { if (!(target instanceof desc.self)) { return new Error(`Expecting ${desc.self}, but has been gotten ${target}`); } else { @@ -80,7 +80,18 @@ export function validateEventDesc(desc: IEventDesc, target: any): Error | undefi (typeof target[name] === typeRef || typeRef === 'any') ) { valid = true; - } else if (typeof typeRef !== 'string' && target[name] instanceof typeRef) { + } else if (typeof typeRef === 'object' && typeRef === null && target[name] === null) { + valid = true; + } else if ( + typeof typeRef === 'object' && + typeRef !== null && + target[name] instanceof typeRef + ) { + valid = true; + } else if ( + (typeRef === null && target[name] === null) || + (typeRef === undefined && target[name] === undefined) + ) { valid = true; } }); diff --git a/application/platform/ipc/event/components/destroyed.ts b/application/platform/ipc/event/components/destroyed.ts new file mode 100644 index 000000000..91edc6207 --- /dev/null +++ b/application/platform/ipc/event/components/destroyed.ts @@ -0,0 +1,6 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; + +@Define({ name: 'EmitComponentsDestroyed' }) +export class Event extends SignatureRequirement {} + +export interface Event extends Interface {} diff --git a/application/platform/ipc/event/components/index.ts b/application/platform/ipc/event/components/index.ts new file mode 100644 index 000000000..07d33be29 --- /dev/null +++ b/application/platform/ipc/event/components/index.ts @@ -0,0 +1,5 @@ +export * as Destroyed from './destroyed'; +export * as LoadingCancelled from './loading_cancelled'; +export * as LoadingDone from './loading_done'; +export * as LoadingError from './loading_error'; +export * as LoadingErrors from './loading_errors'; diff --git a/application/platform/ipc/event/components/loading_cancelled.ts b/application/platform/ipc/event/components/loading_cancelled.ts new file mode 100644 index 000000000..c92284c62 --- /dev/null +++ b/application/platform/ipc/event/components/loading_cancelled.ts @@ -0,0 +1,17 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; + +import * as validator from '../../../env/obj'; +import { LoadingCancelledEvent } from '../../../types/components'; + +@Define({ name: 'EmitComponentsLoadingCancelledEvent' }) +export class Event extends SignatureRequirement { + public event: LoadingCancelledEvent; + + constructor(input: { event: LoadingCancelledEvent }) { + super(); + validator.isObject(input); + this.event = validator.getAsObj(input, 'event'); + } +} + +export interface Event extends Interface {} diff --git a/application/platform/ipc/event/components/loading_done.ts b/application/platform/ipc/event/components/loading_done.ts new file mode 100644 index 000000000..7bb63f797 --- /dev/null +++ b/application/platform/ipc/event/components/loading_done.ts @@ -0,0 +1,17 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; + +import * as validator from '../../../env/obj'; +import { LoadingDoneEvent } from '../../../types/components'; + +@Define({ name: 'EmitComponentsLoadingFieldsDone' }) +export class Event extends SignatureRequirement { + public event: LoadingDoneEvent; + + constructor(input: { event: LoadingDoneEvent }) { + super(); + validator.isObject(input); + this.event = validator.getAsObj(input, 'event'); + } +} + +export interface Event extends Interface {} diff --git a/application/platform/ipc/event/components/loading_error.ts b/application/platform/ipc/event/components/loading_error.ts new file mode 100644 index 000000000..4536e5b71 --- /dev/null +++ b/application/platform/ipc/event/components/loading_error.ts @@ -0,0 +1,17 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; + +import * as validator from '../../../env/obj'; +import { LoadingErrorEvent } from '../../../types/components'; + +@Define({ name: 'EmitComponentsLoadingErrorEvent' }) +export class Event extends SignatureRequirement { + public event: LoadingErrorEvent; + + constructor(input: { event: LoadingErrorEvent }) { + super(); + validator.isObject(input); + this.event = validator.getAsObj(input, 'event'); + } +} + +export interface Event extends Interface {} diff --git a/application/platform/ipc/event/components/loading_errors.ts b/application/platform/ipc/event/components/loading_errors.ts new file mode 100644 index 000000000..a368d4107 --- /dev/null +++ b/application/platform/ipc/event/components/loading_errors.ts @@ -0,0 +1,17 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; + +import * as validator from '../../../env/obj'; +import { LoadingErrorsEvent } from '../../../types/components'; + +@Define({ name: 'EmitComponentsLoadingErrorsEvent' }) +export class Event extends SignatureRequirement { + public event: LoadingErrorsEvent; + + constructor(input: { event: LoadingErrorsEvent }) { + super(); + validator.isObject(input); + this.event = validator.getAsObj(input, 'event'); + } +} + +export interface Event extends Interface {} diff --git a/application/platform/ipc/event/index.ts b/application/platform/ipc/event/index.ts index 305553c9c..76f54bc60 100644 --- a/application/platform/ipc/event/index.ts +++ b/application/platform/ipc/event/index.ts @@ -9,3 +9,4 @@ export * as Cli from './cli'; export * as Logs from './logs'; export * as Values from './values'; export * as GitHub from './github'; +export * as Components from './components'; diff --git a/application/platform/ipc/event/observe/finished.ts b/application/platform/ipc/event/observe/finished.ts index 585aa14a5..7f5cf16c3 100644 --- a/application/platform/ipc/event/observe/finished.ts +++ b/application/platform/ipc/event/observe/finished.ts @@ -1,4 +1,5 @@ import { Define, Interface, SignatureRequirement } from '../declarations'; +import { SessionSetup } from '../../../types/bindings'; import * as validator from '../../../env/obj'; @@ -6,14 +7,14 @@ import * as validator from '../../../env/obj'; export class Event extends SignatureRequirement { public session: string; public operation: string; - public source: string; + public options: SessionSetup; - constructor(input: { session: string; operation: string; source: string }) { + constructor(input: { session: string; operation: string; options: SessionSetup }) { super(); validator.isObject(input); this.session = validator.getAsNotEmptyString(input, 'session'); this.operation = validator.getAsNotEmptyString(input, 'operation'); - this.source = validator.getAsNotEmptyString(input, 'source'); + this.options = validator.getAsObj(input, 'options'); } } diff --git a/application/platform/ipc/event/observe/started.ts b/application/platform/ipc/event/observe/started.ts index 422837356..657716db7 100644 --- a/application/platform/ipc/event/observe/started.ts +++ b/application/platform/ipc/event/observe/started.ts @@ -1,4 +1,5 @@ import { Define, Interface, SignatureRequirement } from '../declarations'; +import { SessionSetup } from '../../../types/bindings'; import * as validator from '../../../env/obj'; @@ -6,14 +7,14 @@ import * as validator from '../../../env/obj'; export class Event extends SignatureRequirement { public session: string; public operation: string; - public source: string; + public options: SessionSetup; - constructor(input: { session: string; operation: string; source: string }) { + constructor(input: { session: string; operation: string; options: SessionSetup }) { super(); validator.isObject(input); this.session = validator.getAsNotEmptyString(input, 'session'); this.operation = validator.getAsNotEmptyString(input, 'operation'); - this.source = validator.getAsNotEmptyString(input, 'source'); + this.options = validator.getAsObj(input, 'options'); } } diff --git a/application/platform/ipc/event/state/job.ts b/application/platform/ipc/event/state/job.ts index 1926eed7d..5b146005b 100644 --- a/application/platform/ipc/event/state/job.ts +++ b/application/platform/ipc/event/state/job.ts @@ -28,7 +28,7 @@ export class Event extends SignatureRequirement { this.session = validator.getAsNotEmptyStringOrAsUndefined(input, 'session'); this.name = validator.getAsNotEmptyStringOrAsUndefined(input, 'name'); this.desc = validator.getAsNotEmptyStringOrAsUndefined(input, 'desc'); - this.icon = validator.getAsNotEmptyStringOrAsUndefined(input, 'icon'); + this.icon = validator.getAsStringOrAsUndefined(input, 'icon'); this.spinner = typeof input.spinner === 'boolean' ? input.spinner : undefined; } } diff --git a/application/platform/ipc/event/stream/index.ts b/application/platform/ipc/event/stream/index.ts index 8be40dd7b..7a4644f1f 100644 --- a/application/platform/ipc/event/stream/index.ts +++ b/application/platform/ipc/event/stream/index.ts @@ -1,3 +1,4 @@ export * as Updated from './updated'; export * as IndexedMapUpdated from './indexed_map_updated'; export * as Attachment from './attachment'; +export * as SessionDescriptor from './session_descriptor'; diff --git a/application/platform/ipc/event/stream/session_descriptor.ts b/application/platform/ipc/event/stream/session_descriptor.ts new file mode 100644 index 000000000..4b85a8b03 --- /dev/null +++ b/application/platform/ipc/event/stream/session_descriptor.ts @@ -0,0 +1,21 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; +import { SessionDescriptor } from '../../../types/bindings'; + +import * as validator from '../../../env/obj'; + +@Define({ name: 'SessionDescriptorUpdated' }) +export class Event extends SignatureRequirement { + public descriptor: SessionDescriptor; + public operation: string; + public session: string; + + constructor(input: { descriptor: SessionDescriptor; session: string; operation: string }) { + super(); + validator.isObject(input); + this.descriptor = validator.getAsObj(input, 'descriptor'); + this.session = validator.getAsNotEmptyString(input, 'session'); + this.operation = validator.getAsNotEmptyString(input, 'operation'); + } +} + +export interface Event extends Interface {} diff --git a/application/platform/ipc/request/actions/open.file.ts b/application/platform/ipc/request/actions/open.file.ts index d2a3c0baf..c8c6bc34a 100644 --- a/application/platform/ipc/request/actions/open.file.ts +++ b/application/platform/ipc/request/actions/open.file.ts @@ -1,5 +1,5 @@ import { Define, Interface, SignatureRequirement } from '../declarations'; -import { FileType } from '../../../types/observe/types/file'; +import { FileType } from '../../../types/files'; import * as validator from '../../../env/obj'; diff --git a/application/platform/ipc/request/actions/open.folder.ts b/application/platform/ipc/request/actions/open.folder.ts index 2d8d582c5..c9a7063b5 100644 --- a/application/platform/ipc/request/actions/open.folder.ts +++ b/application/platform/ipc/request/actions/open.folder.ts @@ -1,5 +1,5 @@ import { Define, Interface, SignatureRequirement } from '../declarations'; -import { FileType } from '../../../types/observe/types/file'; +import { FileType } from '../../../types/files'; import * as validator from '../../../env/obj'; diff --git a/application/platform/ipc/request/actions/stream.ts b/application/platform/ipc/request/actions/stream.ts index eadc44a43..0dd9e9c46 100644 --- a/application/platform/ipc/request/actions/stream.ts +++ b/application/platform/ipc/request/actions/stream.ts @@ -1,19 +1,18 @@ import { Define, Interface, SignatureRequirement } from '../declarations'; -import { Protocol } from '../../../types/observe/parser'; -import { Source } from '../../../types/observe/origin/stream/index'; +import { Ident } from '../../../types/bindings/observe'; import * as validator from '../../../env/obj'; @Define({ name: 'StreamActionRequest' }) export class Request extends SignatureRequirement { - public protocol: Protocol; - public source?: Source; + public parser?: Ident; + public source?: Ident; - constructor(input: { protocol: Protocol; source: Source | undefined }) { + constructor(input: { parser: Ident | undefined; source: Ident | undefined }) { super(); validator.isObject(input); - this.protocol = validator.getAsNotEmptyString(input, 'protocol') as Protocol; - this.source = validator.getAsNotEmptyStringOrAsUndefined(input, 'source') as Source; + this.parser = validator.getAsObjOrUndefined(input, 'parser'); + this.source = validator.getAsObjOrUndefined(input, 'source'); } } export interface Request extends Interface {} diff --git a/application/platform/ipc/request/cli/observe.ts b/application/platform/ipc/request/cli/observe.ts index 4d746469a..39b86dec7 100644 --- a/application/platform/ipc/request/cli/observe.ts +++ b/application/platform/ipc/request/cli/observe.ts @@ -1,22 +1,16 @@ import { Define, Interface, SignatureRequirement } from '../declarations'; -import { IObserve, Observe } from '../../../types/observe/index'; +import { SessionSetup } from '../../../types/bindings/observe'; import * as validator from '../../../env/obj'; @Define({ name: 'ObserveCLICommandRequest' }) export class Request extends SignatureRequirement { - public observe: IObserve[]; + public observe: SessionSetup[]; - constructor(input: { observe: IObserve[] }) { + constructor(input: { observe: SessionSetup[] }) { super(); validator.isObject(input); this.observe = validator.getAsObj(input, 'observe'); - for (const obsv of this.observe) { - const error = Observe.validate(obsv); - if (error instanceof Error) { - throw error; - } - } } } diff --git a/application/platform/ipc/request/components/abort.ts b/application/platform/ipc/request/components/abort.ts new file mode 100644 index 000000000..59ddfe727 --- /dev/null +++ b/application/platform/ipc/request/components/abort.ts @@ -0,0 +1,20 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; + +import * as validator from '../../../env/obj'; + +@Define({ name: 'AbortFieldsLoadingsRequest' }) +export class Request extends SignatureRequirement { + public fields: string[]; + + constructor(input: { fields: string[] }) { + super(); + validator.isObject(input); + this.fields = validator.getAsArray(input, 'fields'); + } +} +export interface Request extends Interface {} + +@Define({ name: 'AbortFieldsLoadingsResponse' }) +export class Response extends SignatureRequirement {} + +export interface Response extends Interface {} diff --git a/application/platform/ipc/request/components/get_compatible_setup.ts b/application/platform/ipc/request/components/get_compatible_setup.ts new file mode 100644 index 000000000..4c936aabc --- /dev/null +++ b/application/platform/ipc/request/components/get_compatible_setup.ts @@ -0,0 +1,29 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; +import { ComponentsList, SessionAction } from '../../../types/bindings'; + +import * as validator from '../../../env/obj'; + +@Define({ name: 'GetCompatibleSetupRequest' }) +export class Request extends SignatureRequirement { + public origin: SessionAction; + + constructor(input: { origin: SessionAction }) { + super(); + validator.isObject(input); + this.origin = input.origin; + } +} +export interface Request extends Interface {} + +@Define({ name: 'GetCompatibleSetupResponse' }) +export class Response extends SignatureRequirement { + public components: ComponentsList; + + constructor(input: { components: ComponentsList }) { + super(); + validator.isObject(input); + this.components = validator.getAsObj(input, 'components'); + } +} + +export interface Response extends Interface {} diff --git a/application/platform/ipc/request/components/get_default_options.ts b/application/platform/ipc/request/components/get_default_options.ts new file mode 100644 index 000000000..3a4f6c0e5 --- /dev/null +++ b/application/platform/ipc/request/components/get_default_options.ts @@ -0,0 +1,31 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; +import { FieldList, SessionAction } from '../../../types/bindings'; + +import * as validator from '../../../env/obj'; + +@Define({ name: 'GetDefaultOptionsRequest' }) +export class Request extends SignatureRequirement { + public uuid: string; + public origin: SessionAction; + + constructor(input: { uuid: string; origin: SessionAction }) { + super(); + validator.isObject(input); + this.uuid = validator.getAsNotEmptyString(input, 'uuid'); + this.origin = input.origin; + } +} +export interface Request extends Interface {} + +@Define({ name: 'GetDefaultOptionsResponse' }) +export class Response extends SignatureRequirement { + public fields: FieldList; + + constructor(input: { fields: FieldList }) { + super(); + validator.isObject(input); + this.fields = validator.getAsArray(input, 'fields'); + } +} + +export interface Response extends Interface {} diff --git a/application/platform/ipc/request/components/get_ident.ts b/application/platform/ipc/request/components/get_ident.ts new file mode 100644 index 000000000..ef7232ac4 --- /dev/null +++ b/application/platform/ipc/request/components/get_ident.ts @@ -0,0 +1,29 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; +import { FieldDesc, Ident, SessionAction } from '../../../types/bindings'; + +import * as validator from '../../../env/obj'; + +@Define({ name: 'GetIdentRequest' }) +export class Request extends SignatureRequirement { + public target: string; + + constructor(input: { target: string }) { + super(); + validator.isObject(input); + this.target = validator.getAsNotEmptyString(input, 'target'); + } +} +export interface Request extends Interface {} + +@Define({ name: 'GetIdentResponse' }) +export class Response extends SignatureRequirement { + public ident: Ident | undefined; + + constructor(input: { ident: Ident | undefined }) { + super(); + validator.isObject(input); + this.ident = validator.getAsObjOrUndefined(input, 'ident'); + } +} + +export interface Response extends Interface {} diff --git a/application/platform/ipc/request/components/get_options.ts b/application/platform/ipc/request/components/get_options.ts new file mode 100644 index 000000000..99878e111 --- /dev/null +++ b/application/platform/ipc/request/components/get_options.ts @@ -0,0 +1,32 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; +import { FieldDesc, SessionAction } from '../../../types/bindings'; + +import * as validator from '../../../env/obj'; + +@Define({ name: 'GetComponentsOptionsRequest' }) +export class Request extends SignatureRequirement { + public targets: string[]; + public origin: SessionAction; + + constructor(input: { origin: SessionAction; targets: string[] }) { + super(); + validator.isObject(input); + this.targets = validator.getAsArray(input, 'targets'); + this.origin = + typeof input.origin === 'string' ? input.origin : validator.getAsObj(input, 'origin'); + } +} +export interface Request extends Interface {} + +@Define({ name: 'GetComponentsOptionsResponse' }) +export class Response extends SignatureRequirement { + public options: Map; + + constructor(input: { options: Map }) { + super(); + validator.isObject(input); + this.options = validator.getAsMap(input, 'options'); + } +} + +export interface Response extends Interface {} diff --git a/application/platform/ipc/request/components/get_output_render.ts b/application/platform/ipc/request/components/get_output_render.ts new file mode 100644 index 000000000..4a69cffbe --- /dev/null +++ b/application/platform/ipc/request/components/get_output_render.ts @@ -0,0 +1,28 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; +import { FieldDesc, OutputRender, SessionAction } from '../../../types/bindings'; + +import * as validator from '../../../env/obj'; + +@Define({ name: 'GetOutputRenderRequest' }) +export class Request extends SignatureRequirement { + public uuid: string; + + constructor(input: { uuid: string }) { + super(); + validator.isObject(input); + this.uuid = validator.getAsNotEmptyString(input, 'uuid'); + } +} +export interface Request extends Interface {} + +@Define({ name: 'GetOutputRenderResponse' }) +export class Response extends SignatureRequirement { + public render: OutputRender | null | undefined; + + constructor(input: { render: OutputRender | null | undefined }) { + super(); + this.render = input.render; + } +} + +export interface Response extends Interface {} diff --git a/application/platform/ipc/request/components/get_parsers.ts b/application/platform/ipc/request/components/get_parsers.ts new file mode 100644 index 000000000..dce82db55 --- /dev/null +++ b/application/platform/ipc/request/components/get_parsers.ts @@ -0,0 +1,30 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; +import { SessionAction, Ident } from '../../../types/bindings'; + +import * as validator from '../../../env/obj'; + +@Define({ name: 'GetAvailableParsersRequest' }) +export class Request extends SignatureRequirement { + public origin: SessionAction; + + constructor(input: { origin: SessionAction }) { + super(); + validator.isObject(input); + this.origin = + typeof input.origin === 'string' ? input.origin : validator.getAsObj(input, 'origin'); + } +} +export interface Request extends Interface {} + +@Define({ name: 'GetAvailableParsersResponse' }) +export class Response extends SignatureRequirement { + public list: Ident[]; + + constructor(input: { list: Ident[] }) { + super(); + validator.isObject(input); + this.list = validator.getAsArray(input, 'list'); + } +} + +export interface Response extends Interface {} diff --git a/application/platform/ipc/request/components/get_sources.ts b/application/platform/ipc/request/components/get_sources.ts new file mode 100644 index 000000000..46b9aa098 --- /dev/null +++ b/application/platform/ipc/request/components/get_sources.ts @@ -0,0 +1,30 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; +import { SessionAction, Ident } from '../../../types/bindings'; + +import * as validator from '../../../env/obj'; + +@Define({ name: 'GetAvailableSourcesRequest' }) +export class Request extends SignatureRequirement { + public origin: SessionAction; + + constructor(input: { origin: SessionAction }) { + super(); + validator.isObject(input); + this.origin = + typeof input.origin === 'string' ? input.origin : validator.getAsObj(input, 'origin'); + } +} +export interface Request extends Interface {} + +@Define({ name: 'GetAvailableSourcesResponse' }) +export class Response extends SignatureRequirement { + public list: Ident[]; + + constructor(input: { list: Ident[] }) { + super(); + validator.isObject(input); + this.list = validator.getAsArray(input, 'list'); + } +} + +export interface Response extends Interface {} diff --git a/application/platform/ipc/request/components/index.ts b/application/platform/ipc/request/components/index.ts new file mode 100644 index 000000000..11f1ab8ab --- /dev/null +++ b/application/platform/ipc/request/components/index.ts @@ -0,0 +1,10 @@ +export * as GetSources from './get_sources'; +export * as GetParsers from './get_parsers'; +export * as GetOptions from './get_options'; +export * as GetIdent from './get_ident'; +export * as GetOutputRender from './get_output_render'; +export * as Abort from './abort'; +export * as Validate from './validate'; +export * as IsSdeSupported from './is_sde_supported'; +export * as GetCompatibleSetup from './get_compatible_setup'; +export * as GetDefaultOptions from './get_default_options'; diff --git a/application/platform/ipc/request/components/is_sde_supported.ts b/application/platform/ipc/request/components/is_sde_supported.ts new file mode 100644 index 000000000..2e210fee7 --- /dev/null +++ b/application/platform/ipc/request/components/is_sde_supported.ts @@ -0,0 +1,31 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; +import { SessionAction } from '../../../types/bindings'; + +import * as validator from '../../../env/obj'; + +@Define({ name: 'IsSdeSupportedRequest' }) +export class Request extends SignatureRequirement { + public uuid: string; + public origin: SessionAction; + + constructor(input: { uuid: string; origin: SessionAction }) { + super(); + validator.isObject(input); + this.uuid = validator.getAsNotEmptyString(input, 'uuid'); + this.origin = input.origin; + } +} +export interface Request extends Interface {} + +@Define({ name: 'IsSdeSupportedResponse' }) +export class Response extends SignatureRequirement { + public support: boolean; + + constructor(input: { support: boolean }) { + super(); + validator.isObject(input); + this.support = validator.getAsBool(input, 'support'); + } +} + +export interface Response extends Interface {} diff --git a/application/platform/ipc/request/components/validate.ts b/application/platform/ipc/request/components/validate.ts new file mode 100644 index 000000000..45e4c37e8 --- /dev/null +++ b/application/platform/ipc/request/components/validate.ts @@ -0,0 +1,34 @@ +import { Define, Interface, SignatureRequirement } from '../declarations'; +import { Field, SessionAction } from '../../../types/bindings'; + +import * as validator from '../../../env/obj'; + +@Define({ name: 'ValidateComponentsOptionsRequest' }) +export class Request extends SignatureRequirement { + public origin: SessionAction; + public target: string; + public fields: Field[]; + + constructor(input: { origin: SessionAction; target: string; fields: Field[] }) { + super(); + validator.isObject(input); + this.target = validator.getAsNotEmptyString(input, 'target'); + this.origin = + typeof input.origin === 'string' ? input.origin : validator.getAsObj(input, 'origin'); + this.fields = validator.getAsArray(input, 'fields'); + } +} +export interface Request extends Interface {} + +@Define({ name: 'ValidateComponentsOptionsResponse' }) +export class Response extends SignatureRequirement { + public errors: Map; + + constructor(input: { errors: Map }) { + super(); + validator.isObject(input); + this.errors = validator.getAsMap(input, 'errors'); + } +} + +export interface Response extends Interface {} diff --git a/application/platform/ipc/request/dlt/index.ts b/application/platform/ipc/request/dlt/index.ts deleted file mode 100644 index 8d9feda93..000000000 --- a/application/platform/ipc/request/dlt/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as Stat from './stat'; diff --git a/application/platform/ipc/request/dlt/stat.ts b/application/platform/ipc/request/dlt/stat.ts deleted file mode 100644 index ebb1364be..000000000 --- a/application/platform/ipc/request/dlt/stat.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Define, Interface, SignatureRequirement } from '../declarations'; -import { DltStatisticInfo } from '../../../types/bindings'; -import * as validator from '../../../env/obj'; - -@Define({ name: 'DltStatRequest' }) -export class Request extends SignatureRequirement { - public files: string[]; - - constructor(input: { files: string[] }) { - super(); - validator.isObject(input); - this.files = validator.getAsArray(input, 'files'); - } -} -export interface Request extends Interface {} - -@Define({ name: 'DltStatResponse' }) -export class Response extends SignatureRequirement { - public stat: DltStatisticInfo; - - constructor(input: { stat: DltStatisticInfo }) { - super(); - validator.isObject(input); - this.stat = validator.getAsObj(input, 'stat'); - } -} - -export interface Response extends Interface {} diff --git a/application/platform/ipc/request/file/select.ts b/application/platform/ipc/request/file/select.ts index c01841b8a..e82af6cff 100644 --- a/application/platform/ipc/request/file/select.ts +++ b/application/platform/ipc/request/file/select.ts @@ -1,6 +1,6 @@ import { Define, Interface, SignatureRequirement } from '../declarations'; import { File } from '../../../types/files'; -import { FileType } from '../../../types/observe/types/file'; +import { FileType } from '../../../types/files'; import * as validator from '../../../env/obj'; diff --git a/application/platform/ipc/request/folder/select.ts b/application/platform/ipc/request/folder/select.ts index 231724530..02f0a1652 100644 --- a/application/platform/ipc/request/folder/select.ts +++ b/application/platform/ipc/request/folder/select.ts @@ -1,6 +1,6 @@ import { Define, Interface, SignatureRequirement } from '../declarations'; import { File } from '../../../types/files'; -import { FileType } from '../../../types/observe/types/file'; +import { FileType } from '../../../types/files'; import * as validator from '../../../env/obj'; diff --git a/application/platform/ipc/request/index.ts b/application/platform/ipc/request/index.ts index 1d074a24b..f13a57711 100644 --- a/application/platform/ipc/request/index.ts +++ b/application/platform/ipc/request/index.ts @@ -2,8 +2,6 @@ export { IpcRequest, InjectLogger } from './declarations'; export * as Session from './session'; export * as File from './file'; export * as Stream from './stream'; -export * as Dlt from './dlt'; -export * as Someip from './someip'; export * as Search from './search'; export * as Storage from './storage'; export * as Os from './os'; @@ -22,3 +20,4 @@ export * as System from './system'; export * as Values from './values'; export * as GitHub from './github'; export * as Plugins from './plugins'; +export * as Components from './components'; diff --git a/application/platform/ipc/request/observe/list.ts b/application/platform/ipc/request/observe/list.ts index f1685d68b..c5bf27d73 100644 --- a/application/platform/ipc/request/observe/list.ts +++ b/application/platform/ipc/request/observe/list.ts @@ -18,13 +18,13 @@ export interface Request extends Interface {} @Define({ name: 'ObserveListResponse' }) export class Response extends SignatureRequirement { public session: string; - public sources: { [key: string]: string }; + public operations: string[]; - constructor(input: { session: string; sources: { [key: string]: string } }) { + constructor(input: { session: string; operations: string[] }) { super(); validator.isObject(input); this.session = validator.getAsNotEmptyString(input, 'session'); - this.sources = validator.getAsObj(input, 'sources'); + this.operations = validator.getAsArray(input, 'operations'); } } diff --git a/application/platform/ipc/request/observe/sources.ts b/application/platform/ipc/request/observe/sources.ts index 9724bc1bf..3c273a465 100644 --- a/application/platform/ipc/request/observe/sources.ts +++ b/application/platform/ipc/request/observe/sources.ts @@ -1,5 +1,5 @@ import { Define, Interface, SignatureRequirement } from '../declarations'; -import { ISourceLink } from '../../../types/observe/types/index'; +import { SourceDefinition } from '../../../types/bindings/miscellaneous'; import * as validator from '../../../env/obj'; @@ -19,13 +19,13 @@ export interface Request extends Interface {} @Define({ name: 'SourcesListResponse' }) export class Response extends SignatureRequirement { public session: string; - public sources: ISourceLink[]; + public sources: SourceDefinition[]; - constructor(input: { session: string; sources: ISourceLink[] }) { + constructor(input: { session: string; sources: SourceDefinition[] }) { super(); validator.isObject(input); this.session = validator.getAsNotEmptyString(input, 'session'); - this.sources = validator.getAsArray(input, 'sources'); + this.sources = validator.getAsArray(input, 'sources'); } } diff --git a/application/platform/ipc/request/observe/start.ts b/application/platform/ipc/request/observe/start.ts index b7516aa7a..b7420aa21 100644 --- a/application/platform/ipc/request/observe/start.ts +++ b/application/platform/ipc/request/observe/start.ts @@ -1,22 +1,18 @@ import { Define, Interface, SignatureRequirement } from '../declarations'; -import { IObserve, Observe } from '../../../types/observe/index'; +import { SessionSetup } from '../../../types/bindings'; import * as validator from '../../../env/obj'; @Define({ name: 'StartObserveRequest' }) export class Request extends SignatureRequirement { public session: string; - public observe: IObserve; + public options: SessionSetup; - constructor(input: { session: string; observe: IObserve }) { + constructor(input: { session: string; options: SessionSetup }) { super(); validator.isObject(input); this.session = validator.getAsNotEmptyString(input, 'session'); - this.observe = validator.getAsObj(input, 'observe'); - const error = Observe.validate(this.observe); - if (error instanceof Error) { - throw error; - } + this.options = validator.getAsObj(input, 'options'); } } @@ -24,13 +20,17 @@ export interface Request extends Interface {} @Define({ name: 'StartObserveResponse' }) export class Response extends SignatureRequirement { + /// Session Uuid public session: string; + /// Operation Uuid + public uuid?: string; public error?: string; - constructor(input: { session: string; error?: string }) { + constructor(input: { session: string; uuid: string | undefined; error?: string }) { super(); validator.isObject(input); this.session = validator.getAsNotEmptyString(input, 'session'); + this.uuid = validator.getAsNotEmptyStringOrAsUndefined(input, 'uuid'); this.error = validator.getAsNotEmptyStringOrAsUndefined(input, 'error'); } } diff --git a/application/platform/ipc/request/someip/index.ts b/application/platform/ipc/request/someip/index.ts deleted file mode 100644 index 48f2d93fd..000000000 --- a/application/platform/ipc/request/someip/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as Statistic from './statistic'; diff --git a/application/platform/ipc/request/someip/statistic.ts b/application/platform/ipc/request/someip/statistic.ts deleted file mode 100644 index 0c3726a69..000000000 --- a/application/platform/ipc/request/someip/statistic.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Define, Interface, SignatureRequirement } from '../declarations'; -import { SomeipStatistic } from '../../../types/observe/parser/someip'; - -import * as validator from '../../../env/obj'; - -@Define({ name: 'SomeipStatisticRequest' }) -export class Request extends SignatureRequirement { - public files: string[]; - - constructor(input: { files: string[] }) { - super(); - validator.isObject(input); - this.files = validator.getAsArray(input, 'files'); - } -} -export interface Request extends Interface {} - -@Define({ name: 'SomeipStatisticResponse' }) -export class Response extends SignatureRequirement { - public statistic: SomeipStatistic; - - constructor(input: { statistic: SomeipStatistic }) { - super(); - validator.isObject(input); - this.statistic = validator.getAsObj(input, 'statistic'); - } -} - -export interface Response extends Interface {} diff --git a/application/platform/package.json b/application/platform/package.json index 865aa4b46..915ce74bf 100644 --- a/application/platform/package.json +++ b/application/platform/package.json @@ -95,25 +95,7 @@ "./types/comment": "./dist/types/comment.js", "./types/bookmark": "./dist/types/bookmark.js", "./types/exporting": "./dist/types/exporting.js", - "./types/observe": "./dist/types/observe/index.js", - "./types/observe/factory": "./dist/types/observe/factory.js", - "./types/observe/description": "./dist/types/observe/description.js", - "./types/observe/parser": "./dist/types/observe/parser/index.js", - "./types/observe/parser/dlt": "./dist/types/observe/parser/dlt/index.js", - "./types/observe/parser/someip": "./dist/types/observe/parser/someip/index.js", - "./types/observe/parser/text": "./dist/types/observe/parser/text/index.js", - "./types/observe/types": "./dist/types/observe/types/index.js", - "./types/observe/types/file": "./dist/types/observe/types/file/index.js", - "./types/observe/types/sourcelink": "./dist/types/observe/types/sourcelink.js", - "./types/observe/origin": "./dist/types/observe/origin/index.js", - "./types/observe/origin/file": "./dist/types/observe/origin/file.js", - "./types/observe/origin/concat": "./dist/types/observe/origin/concat.js", - "./types/observe/origin/stream": "./dist/types/observe/origin/stream.js", - "./types/observe/origin/stream/index": "./dist/types/observe/origin/stream/index.js", - "./types/observe/origin/stream/process": "./dist/types/observe/origin/stream/process/index.js", - "./types/observe/origin/stream/udp": "./dist/types/observe/origin/stream/udp/index.js", - "./types/observe/origin/stream/tcp": "./dist/types/observe/origin/stream/tcp/index.js", - "./types/observe/origin/stream/serial": "./dist/types/observe/origin/stream/serial/index.js", + "./types/sessionsetup": "./dist/types/sessionsetup/index.js", "./types/range": "./dist/types/range.js", "./types/hotkeys/map": "./dist/types/hotkeys/map.js", "./types/parsers": "./dist/types/parsers/index.js", @@ -143,6 +125,7 @@ "./types/github/filemetadata": "./dist/types/github/filemetadata.js", "./types/github/filter": "./dist/types/github/filter.js", "./types/bindings": "./dist/types/bindings/index.js", + "./types/components": "./dist/types/components.js", "./package.json": "./package.json" }, "packageManager": "yarn@4.6.0" diff --git a/application/platform/types/bindings/callback.ts b/application/platform/types/bindings/callback.ts index 96d5e7d47..92558966f 100644 --- a/application/platform/types/bindings/callback.ts +++ b/application/platform/types/bindings/callback.ts @@ -1,61 +1,104 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AttachmentInfo } from "./attachment"; -import type { FilterMatchList } from "./miscellaneous"; -import type { NativeError } from "./error"; -import type { Progress } from "./progress"; +import type { AttachmentInfo } from './attachment'; +import type { FilterMatchList } from './miscellaneous'; +import type { NativeError } from './error'; +import type { Progress } from './progress'; +import type { SessionDescriptor } from './observe'; /** * Represents events sent to the client. */ -export type CallbackEvent = { "StreamUpdated": number } | "FileRead" | { "SearchUpdated": { -/** - * The number of logs with matches. Can be `0` if the search is reset on the client side. - */ -found: number, -/** - * A map of search conditions and their global match counts within the session. - * - `String`: The search condition. - * - `u64`: The count of matches. - */ -stat: Map, } } | { "IndexedMapUpdated": { -/** - * The number of log entries from search results available for reading. - */ -len: number, } } | { "SearchMapUpdated": FilterMatchList | null } | { "SearchValuesUpdated": Map } | { "AttachmentsUpdated": { -/** - * The size of the attachment in bytes. - */ -len: number, -/** - * The description of the attachment. - */ -attachment: AttachmentInfo, } } | { "Progress": { -/** - * The unique identifier of the operation. - */ -uuid: string, -/** - * Information about the progress. - */ -progress: Progress, } } | { "SessionError": NativeError } | { "OperationError": { -/** - * The unique identifier of the operation that caused the error. - */ -uuid: string, -/** - * The error details. - */ -error: NativeError, } } | { "OperationStarted": string } | { "OperationProcessing": string } | { "OperationDone": OperationDone } | "SessionDestroyed"; +export type CallbackEvent = + | { StreamUpdated: number } + | 'FileRead' + | { + SearchUpdated: { + /** + * The number of logs with matches. Can be `0` if the search is reset on the client side. + */ + found: number; + /** + * A map of search conditions and their global match counts within the session. + * - `String`: The search condition. + * - `u64`: The count of matches. + */ + stat: Map; + }; + } + | { + IndexedMapUpdated: { + /** + * The number of log entries from search results available for reading. + */ + len: number; + }; + } + | { SearchMapUpdated: FilterMatchList | null } + | { + SessionDescriptor: { + /** + * Uuid of the observe operation, which adds new `SessionDescriptor` + */ + uuid: string; + /** + * Added `SessionDescriptor` + */ + desc: SessionDescriptor; + }; + } + | { SearchValuesUpdated: Map } + | { + AttachmentsUpdated: { + /** + * The size of the attachment in bytes. + */ + len: number; + /** + * The description of the attachment. + */ + attachment: AttachmentInfo; + }; + } + | { + Progress: { + /** + * The unique identifier of the operation. + */ + uuid: string; + /** + * Information about the progress. + */ + progress: Progress; + }; + } + | { SessionError: NativeError } + | { + OperationError: { + /** + * The unique identifier of the operation that caused the error. + */ + uuid: string; + /** + * The error details. + */ + error: NativeError; + }; + } + | { OperationStarted: string } + | { OperationProcessing: string } + | { OperationDone: OperationDone } + | 'SessionDestroyed'; /** * Contains the results of an operation. */ -export type OperationDone = { -/** - * The unique identifier of the operation. - */ -uuid: string, -/** - * The results of the operation, if available. - */ -result: Array | null, }; +export type OperationDone = { + /** + * The unique identifier of the operation. + */ + uuid: string; + /** + * The results of the operation, if available. + */ + result: Array | null; +}; diff --git a/application/platform/types/bindings/index.ts b/application/platform/types/bindings/index.ts index 1ebc9c2ab..159a5ae86 100644 --- a/application/platform/types/bindings/index.ts +++ b/application/platform/types/bindings/index.ts @@ -9,3 +9,4 @@ export * from './progress'; export * from './dlt'; export * from './operations'; export * from './plugins'; +export * from './options'; diff --git a/application/platform/types/bindings/miscellaneous.ts b/application/platform/types/bindings/miscellaneous.ts index 8b2df171f..e90acfc2a 100644 --- a/application/platform/types/bindings/miscellaneous.ts +++ b/application/platform/types/bindings/miscellaneous.ts @@ -1,4 +1,5 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SessionDescriptor } from './observe'; /** * Data about indices (log entry numbers). Used to provide information about @@ -97,10 +98,14 @@ export type SourceDefinition = { * The unique identifier of the source. */ id: number; + /** + * Parent observe opeartion Uuid + */ + uuid: string; /** * The user-friendly name of the source for display purposes. */ - alias: string; + descriptor: SessionDescriptor; }; /** diff --git a/application/platform/types/bindings/observe.ts b/application/platform/types/bindings/observe.ts index cb65d0fe1..cba0ebaee 100644 --- a/application/platform/types/bindings/observe.ts +++ b/application/platform/types/bindings/observe.ts @@ -1,160 +1,252 @@ import { DltFilterConfig } from './dlt'; +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ComponentOptions } from './options'; +import type { IODataType } from './options'; import type { PluginParserSettings } from './plugins'; -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +export type ComponentDef = { Source: ComponentOptions } | { Parser: ComponentOptions }; /** - * Settings for the DLT parser. - */ -export type DltParserSettings = { -/** - * Configuration for filtering DLT messages. - */ -filter_config: DltFilterConfig, -/** - * Paths to FIBEX files for additional interpretation of `payload` content. + * Represents the type of a component within the system. + * + * The component type indicates the general domain of responsibility and + * functional role of the component. It is used to categorize components + * according to their purpose in the data processing pipeline. */ -fibex_file_paths: Array | null, -/** - * Indicates whether the source contains a `StorageHeader`. Set to `true` if applicable. - */ -with_storage_header: boolean, +export type ComponentType = 'Parser' | 'Source'; + +export type ComponentsList = { parsers: Array; sources: Array }; + /** - * Timezone for timestamp adjustment. If specified, timestamps are converted to this timezone. + * Settings for the DLT parser. */ -tz: string | null, }; +export type DltParserSettings = { + /** + * Configuration for filtering DLT messages. + */ + filter_config: DltFilterConfig; + /** + * Paths to FIBEX files for additional interpretation of `payload` content. + */ + fibex_file_paths: Array | null; + /** + * Indicates whether the source contains a `StorageHeader`. Set to `true` if applicable. + */ + with_storage_header: boolean; + /** + * Timezone for timestamp adjustment. If specified, timestamps are converted to this timezone. + */ + tz: string | null; +}; /** * Supported file formats for observation. */ export type FileFormat = 'PcapNG' | 'PcapLegacy' | 'Text' | 'Binary'; +export type Ident = { + /** + * A short, human-readable name of the component (parser/source), independent of context. + * Used on the client side to display available components. + */ + name: string; + /** + * A more detailed description of the component. This information is displayed in a separate + * area and can contain an extended explanation. + */ + desc: string; + /** + * The type of data the component produces or expects. + * For sources, this is the type of data they emit. + * For parsers, this is the type of data they expect as input. + */ + io: IODataType; + /** + * A unique identifier of the component. + */ + uuid: string; +}; + +export type IdentList = Array; + /** * Multicast configuration information. * - `multiaddr`: A valid multicast address. * - `interface`: The address of the local interface used to join the multicast group. * If set to `INADDR_ANY`, the system selects an appropriate interface. */ -export type MulticastInfo = { multiaddr: string, interface: string | null, }; +export type MulticastInfo = { multiaddr: string; interface: string | null }; /** * Options for observing data within a session. */ -export type ObserveOptions = { -/** - * The description of the data source. - */ -origin: ObserveOrigin, -/** - * The parser configuration to be applied. - */ -parser: ParserType, }; +export type ObserveOptions = { + /** + * The description of the data source. + */ + origin: ObserveOrigin; + /** + * The parser configuration to be applied. + */ + parser: ParserType; +}; /** * Describes the source of data for observation. */ -export type ObserveOrigin = { "File": [string, FileFormat, string] } | { "Concat": Array<[string, FileFormat, string]> } | { "Stream": [string, Transport] }; +export type ObserveOrigin = + | { File: [string, FileFormat, string] } + | { Concat: Array<[string, FileFormat, string]> } + | { Stream: [string, Transport] }; /** * Specifies the parser to be used for processing session data. */ -export type ParserType = { "Dlt": DltParserSettings } | { "SomeIp": SomeIpParserSettings } | { "Text": null } | { Plugin: PluginParserSettings }; +export type ParserType = + | { Dlt: DltParserSettings } + | { SomeIp: SomeIpParserSettings } + | { Text: null } + | { Plugin: PluginParserSettings }; /** * Configuration for executing terminal commands. */ -export type ProcessTransportConfig = { -/** - * The working directory for the command. - */ -cwd: string, -/** - * The command to execute. - */ -command: string, -/** - * Environment variables. If empty, the default environment variables are used. - */ -envs: Map, }; +export type ProcessTransportConfig = { + /** + * The working directory for the command. + */ + cwd: string; + /** + * The command to execute. + */ + command: string; + /** + * Environment variables. If empty, the default environment variables are used. + */ + envs: Map; +}; /** * Configuration for serial port connections. */ -export type SerialTransportConfig = { -/** - * The path to the serial port. - */ -path: string, -/** - * The baud rate for the connection. - */ -baud_rate: number, -/** - * The number of data bits per frame. - */ -data_bits: number, -/** - * The flow control setting. - */ -flow_control: number, -/** - * The parity setting. - */ -parity: number, -/** - * The number of stop bits. - */ -stop_bits: number, -/** - * The delay in sending data, in milliseconds. - */ -send_data_delay: number, +export type SerialTransportConfig = { + /** + * The path to the serial port. + */ + path: string; + /** + * The baud rate for the connection. + */ + baud_rate: number; + /** + * The number of data bits per frame. + */ + data_bits: number; + /** + * The flow control setting. + */ + flow_control: number; + /** + * The parity setting. + */ + parity: number; + /** + * The number of stop bits. + */ + stop_bits: number; + /** + * The delay in sending data, in milliseconds. + */ + send_data_delay: number; + /** + * Whether the connection is exclusive. + */ + exclusive: boolean; +}; + /** - * Whether the connection is exclusive. + * Described user basic action. Source means, user is selecting custom source & parser */ -exclusive: boolean, }; +export type SessionAction = + | { File: string } + | { Files: Array } + | { ExportRaw: [Array, Array<{ start: bigint; end: bigint }>, string] } + | 'Source'; + +export type SessionDescriptor = { + /** + * Identifier of the parser being used. Provides a general description of the parser. + */ + parser: Ident; + /** + * Identifier of the source being used. Provides a general description of the source. + */ + source: Ident; + /** + * Parser description in the context of the current session. + */ + p_desc: string | null; + /** + * Source description in the context of the current session. For example, instead of "UDP", it might be "UDP on 0.0.0.0::8888". + */ + s_desc: string | null; +}; + +export type SessionSetup = { + origin: SessionAction; + parser: ComponentOptions; + source: ComponentOptions; +}; /** * Settings for the SomeIp parser. */ -export type SomeIpParserSettings = { -/** - * Paths to FIBEX files for additional interpretation of `payload` content. - */ -fibex_file_paths: Array | null, }; +export type SomeIpParserSettings = { + /** + * Paths to FIBEX files for additional interpretation of `payload` content. + */ + fibex_file_paths: Array | null; +}; /** * Configuration for TCP connections. */ -export type TCPTransportConfig = { -/** - * The address to bind the TCP connection to. - */ -bind_addr: string, }; +export type TCPTransportConfig = { + /** + * The address to bind the TCP connection to. + */ + bind_addr: string; +}; /** * Describes the transport source for a session. */ -export type Transport = { "Process": ProcessTransportConfig } | { "TCP": TCPTransportConfig } | { "UDP": UDPTransportConfig } | { "Serial": SerialTransportConfig }; +export type Transport = + | { Process: ProcessTransportConfig } + | { TCP: TCPTransportConfig } + | { UDP: UDPTransportConfig } + | { Serial: SerialTransportConfig }; /** * Configuration for UDP connections. */ -export type UDPTransportConfig = { -/** - * The address to bind the UDP connection to. - */ -bind_addr: string, -/** - * A list of multicast configurations. - */ -multicast: Array, }; +export type UDPTransportConfig = { + /** + * The address to bind the UDP connection to. + */ + bind_addr: string; + /** + * A list of multicast configurations. + */ + multicast: Array; +}; /** * Configuration for UDP connections. */ -export type UdpConnectionInfo = { -/** - * A list of multicast addresses to listen on. - */ -multicast_addr: Array, }; +export type UdpConnectionInfo = { + /** + * A list of multicast addresses to listen on. + */ + multicast_addr: Array; +}; diff --git a/application/platform/types/bindings/options.ts b/application/platform/types/bindings/options.ts new file mode 100644 index 000000000..8aee577bc --- /dev/null +++ b/application/platform/types/bindings/options.ts @@ -0,0 +1,85 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Represents events sent to the client. + */ +export type CallbackOptionsEvent = + | { LoadingDone: { owner: string; fields: Array } } + | { LoadingErrors: { owner: string; errors: Array } } + | { LoadingError: { owner: string; error: string; fields: Array } } + | { LoadingCancelled: { owner: string; fields: Array } } + | 'Destroyed'; + +export type ComponentOptions = { fields: Array; uuid: string }; + +export type ComponentsOptionsList = { options: Map }; + +export type Field = { id: string; value: Value }; + +export type FieldDesc = { Static: StaticFieldDesc } | { Lazy: LazyFieldDesc }; + +export type FieldList = Array; + +export type FieldLoadingError = { id: string; err: string }; + +export type FieldsValidationErrors = { errors: Map }; + +export type IODataType = + | 'PlaitText' + | 'Raw' + | 'NetworkFramePayload' + | { Multiple: Array } + | 'Any'; + +export type LazyFieldDesc = { id: string; name: string; desc: string; binding: string | null }; + +export type OutputRender = { Columns: Array<[string, number]> } | 'PlaitText'; + +export type StaticFieldDesc = { + id: string; + name: string; + desc: string; + required: boolean; + interface: ValueInput; + binding: string | null; +}; + +export type Value = + | { Boolean: boolean } + | { Number: number } + | { Numbers: Array } + | { String: string } + | { Strings: Array } + | { Directories: Array } + | { Files: Array } + | { File: string } + | { Directory: string } + | { KeyNumber: Map } + | { KeyNumbers: Map } + | { KeyString: Map } + | { KeyStrings: Map } + | { Fields: Array }; + +export type ValueInput = + | { Checkbox: boolean } + | { Number: number } + | { String: [string, string] } + | { Numbers: [Array, number] } + | { Strings: [Array, string] } + | { NamedBools: Array<[string, boolean]> } + | { NamedNumbers: [Array<[string, number]>, number] } + | { NamedStrings: [Array<[string, string]>, string] } + | { KeyNumber: Map } + | { KeyNumbers: Map } + | { KeyString: Map } + | { KeyStrings: Map } + | { NestedNumbersMap: [Map>>, Map] } + | { NestedStringsMap: [Map>>, Map] } + | 'Directories' + | { Files: Array } + | { File: Array } + | { Directory: string | null } + | 'Timezone' + | { InputsCollection: { elements: Array; add_title: string } } + | { FieldsCollection: { elements: Array; add_title: string } } + | { Bound: { output: ValueInput; inputs: Array } }; diff --git a/application/platform/types/components.ts b/application/platform/types/components.ts new file mode 100644 index 000000000..c472c8947 --- /dev/null +++ b/application/platform/types/components.ts @@ -0,0 +1,21 @@ +import { FieldLoadingError, StaticFieldDesc } from './bindings'; + +export interface LoadingDoneEvent { + owner: string; + fields: StaticFieldDesc[]; +} + +export interface LoadingErrorsEvent { + owner: string; + errors: FieldLoadingError[]; +} + +export interface LoadingErrorEvent { + owner: string; + error: string; + fields: string[]; +} +export interface LoadingCancelledEvent { + owner: string; + fields: string[]; +} diff --git a/application/platform/types/files.ts b/application/platform/types/files.ts index dc97a3862..35929da76 100644 --- a/application/platform/types/files.ts +++ b/application/platform/types/files.ts @@ -1,7 +1,45 @@ -import { FileType } from './observe/types/file'; - import * as obj from '../env/obj'; +export abstract class Support { + public abstract getSupportedFileType(): FileName[]; +} + +// TODO: remove it! +export enum FileType { + PcapNG = 'PcapNG', + PcapLegacy = 'PcapLegacy', + Text = 'Text', + Binary = 'Binary', + ParserPlugin = 'ParserPlugin', +} + +export function getFileTypeFrom(smth: unknown): FileType | Error { + switch (smth as FileType) { + case FileType.Binary: + return FileType.Binary; + case FileType.PcapNG: + return FileType.PcapNG; + case FileType.PcapLegacy: + return FileType.PcapLegacy; + case FileType.Text: + return FileType.Text; + case FileType.ParserPlugin: + return FileType.ParserPlugin; + default: + return new Error(`${smth} isn't FileType`); + } +} + +export function extname(filename: string): string { + const matches = filename.match(/\.[\w\d_]*$/gi); + if (matches !== null) { + return matches[0]; + } + return ''; +} + +export type FileName = string; + export enum EntityType { BlockDevice = 0, CharacterDevice = 1, diff --git a/application/platform/types/index.ts b/application/platform/types/index.ts index 27fce49da..9fc82fe97 100644 --- a/application/platform/types/index.ts +++ b/application/platform/types/index.ts @@ -1,7 +1,6 @@ export * as files from './files'; export * as filter from './filter'; export * as chart from './chart'; -export * as observe from './observe'; export * as content from './content'; export * as range from './range'; export * as storage from './storage'; @@ -10,3 +9,4 @@ export * as comment from './comment'; export * as bookmark from './bookmark'; export * as sde from './sde'; export * as bindings from './bindings'; +export * as components from './components'; diff --git a/application/platform/types/observe/compatibility.ts b/application/platform/types/observe/compatibility.ts deleted file mode 100644 index 876dbf0bf..000000000 --- a/application/platform/types/observe/compatibility.ts +++ /dev/null @@ -1,91 +0,0 @@ -import * as Parser from './parser'; -import * as Stream from './origin/stream/index'; -import * as Origin from './origin/index'; -import * as File from './types/file'; - -export const Streams: { - [key: string]: Stream.Reference[]; -} = { - [Parser.Protocol.Dlt]: [ - // Supported streams - Stream.TCP.Configuration, - Stream.UDP.Configuration, - ], - [Parser.Protocol.SomeIp]: [ - // Supported streams - Stream.UDP.Configuration, - ], - [Parser.Protocol.Text]: [ - // Supported streams - Stream.Serial.Configuration, - Stream.Process.Configuration, - ], - // Make sure we support all kinds of stream - [Parser.Protocol.Plugin]: [ - Stream.TCP.Configuration, - Stream.UDP.Configuration, - Stream.Serial.Configuration, - Stream.Process.Configuration, - ], -}; - -export const Files: { - [key: string]: File.FileType[]; -} = { - [Parser.Protocol.Dlt]: [ - // Supported file types - File.FileType.Binary, - File.FileType.PcapNG, - File.FileType.PcapLegacy, - ], - [Parser.Protocol.SomeIp]: [ - // Supported file types - File.FileType.PcapNG, - File.FileType.PcapLegacy, - ], - [Parser.Protocol.Text]: [ - // Supported file types - File.FileType.Text, - ], - // Plugins support all file types. - [Parser.Protocol.Plugin]: [ - File.FileType.Text, - File.FileType.Binary, - File.FileType.PcapNG, - File.FileType.PcapLegacy, - File.FileType.ParserPlugin, - ], -}; - -export const SDESupport: { - [key: string]: boolean; -} = { - [Origin.Context.File]: false, - [Origin.Context.Concat]: false, - [Origin.Context.Plugin]: false, - [Stream.Source.Process]: true, - [Stream.Source.Serial]: true, - [Stream.Source.TCP]: false, - [Stream.Source.UDP]: false, -}; - -export const Configurable: { - [key: string]: - | { - [key: string]: boolean; - } - | boolean; -} = { - [Origin.Context.File]: { - [Parser.Protocol.Text]: false, - [Parser.Protocol.Dlt]: true, - [Parser.Protocol.SomeIp]: true, - [Parser.Protocol.Plugin]: true, - }, - [Origin.Context.Concat]: true, - [Origin.Context.Plugin]: true, - [Stream.Source.Process]: true, - [Stream.Source.Serial]: true, - [Stream.Source.TCP]: true, - [Stream.Source.UDP]: true, -}; diff --git a/application/platform/types/observe/configuration.ts b/application/platform/types/observe/configuration.ts deleted file mode 100644 index 431238743..000000000 --- a/application/platform/types/observe/configuration.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { error } from '../../log/utils'; -import { JsonConvertor } from '../storage/json'; -import { Validate, SelfValidate, Alias, Destroy, Storable, Hash } from '../env/types'; -import { List } from './description'; -import { scope } from '../../env/scope'; -import { Subject, Subscriber, Subscription } from '../../env/subscription'; -import { unique } from '../../env/sequence'; -import { Observer } from '../../env/observer'; - -import * as Stream from './origin/stream/index'; -import * as File from './types/file'; - -export interface ConfigurationStatic extends Validate, Alias { - initial(): T; -} - -export interface ConfigurationStaticDesc extends Validate, Alias, List { - initial(): T; - inited(): boolean; -} - -export interface Reference extends ConfigurationStatic { - // new (configuration: T, linked: Linked | undefined): C & Configuration; - new (...args: any[]): C & Configuration; -} - -export interface ReferenceDesc extends ConfigurationStaticDesc { - // new (configuration: T, linked: Linked | undefined): C & Configuration; - new (...args: any[]): C & Configuration; -} - -export interface ICompatibilityMod { - Streams: { - [key: string]: Stream.Reference[]; - }; - Files: { - [key: string]: File.FileType[]; - }; - SDESupport: { - [key: string]: boolean; - }; - Configurable: { - [key: string]: - | { - [key: string]: boolean; - } - | boolean; - }; -} -// To prevent circle dependency we are loading compatibility table in async way -let compatibility: ICompatibilityMod | undefined; -import('./compatibility') - .then((mod) => { - compatibility = mod; - }) - .catch((err: Error) => { - scope.getLogger('CompatibilityModule').error(err.message); - }); - -export function getCompatibilityMod(): ICompatibilityMod { - if (compatibility === undefined) { - throw new Error(`Moudle "compatibility" isn't loaded yet`); - } - return compatibility; -} - -export type TOverwriteHandler = (configuration: T) => T; - -export interface Linked { - overwrite: TOverwriteHandler; - watcher: Subject; -} - -export abstract class Configuration - extends Subscriber - implements - JsonConvertor>, - SelfValidate, - Storable, - Hash, - Destroy -{ - protected ref: Reference; - protected readonly src: T; - protected readonly observer: Observer | undefined; - protected readonly linked: Linked | undefined; - protected overwriting: boolean = false; - - public readonly uuid: string = unique(); - public watcher: Subject = new Subject(); - - protected getOriginWatcher(): Subject { - if (this.linked !== undefined) { - return this.linked.watcher; - } - if (this.observer !== undefined) { - return this.observer.watcher; - } - throw new Error(`002: Configuration doesn't have observer or linked`); - } - - constructor(configuration: T, linked: Linked | undefined) { - super(); - if (typeof this.constructor !== 'function') { - throw new Error(`Fail to get reference to Constructor`); - } - this.ref = this.constructor as Reference; - this.src = configuration; - this.linked = linked; - if (linked === undefined) { - this.observer = new Observer(configuration); - } - this.register( - this.getOriginWatcher().subscribe(() => { - if (this.overwriting) { - return; - } - this.watcher.emit(); - }), - ); - } - - public get configuration(): T { - if (this.linked !== undefined) { - return this.src; - } - if (this.observer !== undefined) { - return this.observer.target; - } - throw new Error(`001: Configuration doesn't have observer or linked`); - } - - public destroy(): void { - if (this.observer !== undefined) { - this.observer.destroy(); - } - this.watcher.destroy(); - this.unsubscribe(); - } - - public validate(): Error | undefined { - const error: Error | T = this.ref.validate(this.sterilized()); - return error instanceof Error ? error : undefined; - } - - public alias(): A { - return this.ref.alias(); - } - - public overwrite(configuration: T): void { - this.overwriting = true; - if (this.linked !== undefined) { - (this as any).src = this.linked.overwrite(configuration); - } else if (this.observer !== undefined) { - (this as any).src = Observer.sterilize(configuration); - this.observer.overwrite(configuration); - } else { - this.overwriting = false; - throw new Error(`004: Configuration doesn't have observer or linked`); - } - this.overwriting = false; - this.watcher.emit(); - } - - public setRef(configuration: T | C): void { - if (this.linked !== undefined) { - (this as any).src = configuration; - } else if (this.observer !== undefined) { - throw new Error(`005: Only linked configuration can be rerefered to new target`); - } else { - throw new Error(`006: Configuration doesn't have observer or linked`); - } - } - - public subscribe(handler: () => void): Subscription { - return this.watcher.subscribe(() => { - setTimeout(handler); - }); - } - - public json(): { - to(): string; - from(str: string): Configuration | Error; - } { - return { - to: (): string => { - if (this.linked !== undefined) { - return JSON.stringify(Observer.sterilize(this.src)); - } - if (this.observer !== undefined) { - return JSON.stringify(this.observer.sterilize()); - } - throw new Error(`007: Configuration doesn't have observer or linked`); - }, - from: (str: string): Configuration | Error => { - try { - const configuration: T | Error = this.ref.validate(JSON.parse(str)); - if (configuration instanceof Error) { - return configuration; - } - this.overwrite(configuration); - return this; - } catch (e) { - return new Error(error(e)); - } - }, - }; - } - - public getSupportedStream(): Stream.Reference[] { - if (getCompatibilityMod().Streams[this.ref.alias() as string] === undefined) { - throw new Error( - `008: Entity "${this.ref.alias()}" isn't registred in compatibility.Streams list`, - ); - } - return getCompatibilityMod().Streams[this.ref.alias() as string]; - } - - public getSupportedFileType(): File.FileType[] { - if (getCompatibilityMod().Files[this.ref.alias() as string] === undefined) { - throw new Error( - `009: Entity "${this.ref.alias()}" isn't registred in compatibility.Files list`, - ); - } - return getCompatibilityMod().Files[this.ref.alias() as string]; - } - - public isSdeSupported(): boolean { - if (getCompatibilityMod().SDESupport[this.ref.alias() as string] === undefined) { - throw new Error( - `010: Entity "${this.ref.alias()}" isn't registred in compatibility.SDESupport list`, - ); - } - return getCompatibilityMod().SDESupport[this.ref.alias() as string]; - } - - public sterilized(): T { - if (this.linked !== undefined) { - return Observer.sterilize(this.src); - } - if (this.observer !== undefined) { - return this.observer.sterilize(); - } - throw new Error(`011: Configuration doesn't have observer or linked`); - } - - // This is default implementation, but in some cases (like "Stream.Process") - // it should be adjusted - public storable(): T { - return this.sterilized(); - } - - public abstract hash(): number; -} diff --git a/application/platform/types/observe/description.ts b/application/platform/types/observe/description.ts deleted file mode 100644 index e5bebc40f..000000000 --- a/application/platform/types/observe/description.ts +++ /dev/null @@ -1,39 +0,0 @@ -export interface IList { - major: string; - minor: string; - icon: string | undefined; -} - -export interface IJob { - name: string; - desc: string; - icon: string | undefined; -} - -export enum OriginType { - net = 'net', - serial = 'serial', - command = 'command', - file = 'file', - plugin = 'plugin', -} -export interface IOriginDetails extends IList { - type: OriginType; - action: string; - state: { - running: string; - stopped: string; - }; -} - -export abstract class List { - public abstract desc(): IList; -} - -export abstract class Job { - public abstract asJob(): IJob; -} - -export abstract class OriginDetails { - public abstract desc(): IOriginDetails; -} diff --git a/application/platform/types/observe/factory.ts b/application/platform/types/observe/factory.ts deleted file mode 100644 index 794e18721..000000000 --- a/application/platform/types/observe/factory.ts +++ /dev/null @@ -1,370 +0,0 @@ -import * as $ from './index'; - -export { FileType } from './types/file/index'; - -class Factory { - protected changes: { parser: boolean; origin: boolean } = { parser: false, origin: false }; - - protected updated(): { - parser(): void; - origin(): void; - } { - const update = () => { - if (this.changes.origin === this.changes.parser) { - // No needs to update if and parser and origin were changed or weren't changed both - return; - } - if (this.changes.origin) { - const parsers = this.observe.origin.getSupportedParsers(); - if (parsers.length === 0) { - throw new Error( - `No supported parser for ${this.observe.origin.instance.alias()}`, - ); - } - this.protocol(parsers[0].alias()); - } else if (this.changes.parser) { - // TODO: check source also - // switch (this.observe.origin.instance.alias()) { - // case $.Origin.Context.File: - // const _filetypes = this.observe.parser.getSupportedFileType(); - // break; - // case $.Origin.Context.Concat: - // const _filetypes = this.observe.parser.getSupportedFileType(); - // break; - // case $.Origin.Context.Stream: - // const _streams = this.observe.parser - // .getSupportedStream() - // .map((r) => r.alias()); - // break; - // } - } - }; - return { - parser: (): void => { - this.changes.parser = true; - update(); - }, - origin: (): void => { - this.changes.origin = true; - update(); - }, - }; - } - - protected observe: $.Observe = $.Observe.new(); - - public protocol(protocol: $.Parser.Protocol): T { - this.observe.parser.change($.Parser.getByAlias(protocol)); - this.updated().parser(); - return this as unknown as T; - } - - public asDlt(configuration?: $.Parser.Dlt.IConfiguration): T { - this.observe.parser.change( - new $.Parser.Dlt.Configuration( - configuration === undefined ? $.Parser.Dlt.Configuration.initial() : configuration, - undefined, - ), - ); - this.updated().parser(); - return this as unknown as T; - } - - public asSomeip(configuration?: $.Parser.SomeIp.IConfiguration): T { - this.observe.parser.change( - new $.Parser.SomeIp.Configuration( - configuration === undefined - ? $.Parser.SomeIp.Configuration.initial() - : configuration, - undefined, - ), - ); - this.updated().parser(); - return this as unknown as T; - } - - public parser(parser: $.Parser.Declaration): T { - this.observe.parser.change(parser); - return this as unknown as T; - } - - public asText(): T { - this.observe.parser.change(new $.Parser.Text.Configuration(null, undefined)); - this.updated().parser(); - return this as unknown as T; - } - - public asParserPlugin(configuration?: $.Parser.Plugin.IConfiguration): T { - this.observe.parser.change( - new $.Parser.Plugin.Configuration( - configuration === undefined - ? $.Parser.Plugin.Configuration.initial() - : configuration, - - undefined, - ), - ); - this.updated().parser(); - return this as unknown as T; - } - - public guessParser(): T { - const parsers = this.observe.origin.getSupportedParsers(); - if (parsers.length === 0) { - throw new Error( - `Origin "${this.observe.origin.getNatureAlias()}" doesn't have any suitable parseres.`, - ); - } - const Ref = parsers[0]; - this.observe.parser.change(new Ref(Ref.initial(), undefined)); - this.updated().parser(); - return this as unknown as T; - } - - public get(): $.Observe { - const parsers = this.observe.origin.getSupportedParsers().map((ref) => ref.alias()); - const selected = this.observe.parser.alias(); - if (!parsers.includes(selected)) { - throw new Error( - `Origin "${this.observe.origin.getNatureAlias()}" doesn't support parser: ${selected}; available parsers: ${parsers.join( - ', ', - )}.`, - ); - } - return this.observe; - } -} - -export class File extends Factory { - static FileType = $.Types.File.FileType; - - constructor() { - super(); - this.observe.origin.change( - new $.Origin.File.Configuration($.Origin.File.Configuration.initial(), undefined), - ); - } - - public alias(alias: string): File { - if (!(this.observe.origin.instance instanceof $.Origin.File.Configuration)) { - throw new Error(`Given observe object doesn't have File origin`); - } - this.observe.origin.instance.set().alias(alias); - this.updated().origin(); - return this; - } - - public file(filename: string): File { - if (!(this.observe.origin.instance instanceof $.Origin.File.Configuration)) { - throw new Error(`Given observe object doesn't have File origin`); - } - this.observe.origin.instance.set().filename(filename); - this.updated().origin(); - return this; - } - - public type(type: $.Types.File.FileType): File { - if (!(this.observe.origin.instance instanceof $.Origin.File.Configuration)) { - throw new Error(`Given observe object doesn't have File origin`); - } - this.observe.origin.instance.set().type(type); - this.updated().origin(); - return this; - } -} - -export class Concat extends Factory { - static FileType = $.Types.File.FileType; - - constructor() { - super(); - this.observe.origin.change( - new $.Origin.Concat.Configuration($.Origin.Concat.Configuration.initial(), undefined), - ); - } - - public files(files: string[]): Concat { - if (!(this.observe.origin.instance instanceof $.Origin.Concat.Configuration)) { - throw new Error(`Given observe object doesn't have Concat origin`); - } - this.observe.origin.instance.set().files(files); - this.updated().origin(); - return this; - } - - public type(type: $.Types.File.FileType): Concat { - if (!(this.observe.origin.instance instanceof $.Origin.Concat.Configuration)) { - throw new Error(`Given observe object doesn't have Concat origin`); - } - this.observe.origin.instance.set().defaults(type); - this.updated().origin(); - return this; - } - - public push(filename: string, type: $.Types.File.FileType): Concat { - if (!(this.observe.origin.instance instanceof $.Origin.Concat.Configuration)) { - throw new Error(`Given observe object doesn't have Concat origin`); - } - this.observe.origin.instance.set().push(filename, type); - this.updated().origin(); - return this; - } - - public remove(filename: string): Concat { - if (!(this.observe.origin.instance instanceof $.Origin.Concat.Configuration)) { - throw new Error(`Given observe object doesn't have Concat origin`); - } - this.observe.origin.instance.set().remove(filename); - this.updated().origin(); - return this; - } -} - -export class Stream extends Factory { - constructor() { - super(); - this.observe.origin.change( - new $.Origin.Stream.Configuration($.Origin.Stream.Configuration.initial(), undefined), - ); - } - - public process(configuration?: $.Origin.Stream.Stream.Process.IConfiguration): Stream { - if (!(this.observe.origin.instance instanceof $.Origin.Stream.Configuration)) { - throw new Error(`Given observe object doesn't have Stream origin`); - } - this.observe.origin.instance.change( - new $.Origin.Stream.Stream.Process.Configuration( - configuration !== undefined - ? configuration - : $.Origin.Stream.Stream.Process.Configuration.initial(), - undefined, - ), - ); - this.updated().origin(); - return this; - } - - public serial(configuration?: $.Origin.Stream.Stream.Serial.IConfiguration): Stream { - if (!(this.observe.origin.instance instanceof $.Origin.Stream.Configuration)) { - throw new Error(`Given observe object doesn't have Stream origin`); - } - this.observe.origin.instance.change( - new $.Origin.Stream.Stream.Serial.Configuration( - configuration !== undefined - ? configuration - : $.Origin.Stream.Stream.Serial.Configuration.initial(), - undefined, - ), - ); - this.updated().origin(); - return this; - } - - public tcp(configuration?: $.Origin.Stream.Stream.TCP.IConfiguration): Stream { - if (!(this.observe.origin.instance instanceof $.Origin.Stream.Configuration)) { - throw new Error(`Given observe object doesn't have Stream origin`); - } - this.observe.origin.instance.change( - new $.Origin.Stream.Stream.TCP.Configuration( - configuration !== undefined - ? configuration - : $.Origin.Stream.Stream.TCP.Configuration.initial(), - undefined, - ), - ); - this.updated().origin(); - return this; - } - - public udp(configuration?: $.Origin.Stream.Stream.UDP.IConfiguration): Stream { - if (!(this.observe.origin.instance instanceof $.Origin.Stream.Configuration)) { - throw new Error(`Given observe object doesn't have Stream origin`); - } - this.observe.origin.instance.change( - new $.Origin.Stream.Stream.UDP.Configuration( - configuration !== undefined - ? configuration - : $.Origin.Stream.Stream.UDP.Configuration.initial(), - undefined, - ), - ); - this.updated().origin(); - return this; - } -} - -export function map(observe: $.Observe): { - file(): boolean; - concat(): boolean; - process(): boolean; - serial(): boolean; - udp(): boolean; - tcp(): boolean; -} { - return { - file: (): boolean => { - return ( - observe.origin.as<$.Origin.File.Configuration>($.Origin.File.Configuration) !== - undefined - ); - }, - concat: (): boolean => { - return ( - observe.origin.as<$.Origin.Concat.Configuration>($.Origin.Concat.Configuration) !== - undefined - ); - }, - process: (): boolean => { - const stream = observe.origin.as<$.Origin.Stream.Configuration>( - $.Origin.Stream.Configuration, - ); - if (stream === undefined) { - return false; - } - return ( - stream.as<$.Origin.Stream.Stream.Process.Configuration>( - $.Origin.Stream.Stream.Process.Configuration, - ) !== undefined - ); - }, - serial: (): boolean => { - const stream = observe.origin.as<$.Origin.Stream.Configuration>( - $.Origin.Stream.Configuration, - ); - if (stream === undefined) { - return false; - } - return ( - stream.as<$.Origin.Stream.Stream.Serial.Configuration>( - $.Origin.Stream.Stream.Serial.Configuration, - ) !== undefined - ); - }, - udp: (): boolean => { - const stream = observe.origin.as<$.Origin.Stream.Configuration>( - $.Origin.Stream.Configuration, - ); - if (stream === undefined) { - return false; - } - return ( - stream.as<$.Origin.Stream.Stream.UDP.Configuration>( - $.Origin.Stream.Stream.UDP.Configuration, - ) !== undefined - ); - }, - tcp: (): boolean => { - const stream = observe.origin.as<$.Origin.Stream.Configuration>( - $.Origin.Stream.Configuration, - ); - if (stream === undefined) { - return false; - } - return ( - stream.as<$.Origin.Stream.Stream.TCP.Configuration>( - $.Origin.Stream.Stream.TCP.Configuration, - ) !== undefined - ); - }, - }; -} diff --git a/application/platform/types/observe/index.ts b/application/platform/types/observe/index.ts deleted file mode 100644 index 7cba9c3d7..000000000 --- a/application/platform/types/observe/index.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { Configuration as Base, getCompatibilityMod } from './configuration'; -import { Mutable } from '../unity/mutable'; -import { LockToken } from '../../env/lock.token'; -import { Signature } from '../env/types'; - -import * as Parser from './parser'; -import * as Origin from './origin'; -import * as Sde from './sde'; - -export * as Parser from './parser'; -export * as Origin from './origin'; -export * as Types from './types'; -export * as Description from './description'; - -export { IList, IOriginDetails, IJob } from './description'; - -export interface IObserve { - origin: Origin.IConfiguration; - parser: Parser.IConfiguration; -} - -export class Observe - extends Base - implements Sde.Support, Parser.Support, Signature -{ - static new(): Observe { - return new Observe({ - parser: { - Dlt: Parser.Dlt.Configuration.initial(), - }, - origin: { - Stream: Origin.Stream.Configuration.initial(), - }, - }); - } - - static from(json: string): Observe | Error { - const observe = new Observe({ - parser: { - Dlt: Parser.Dlt.Configuration.initial(), - }, - origin: { - Stream: Origin.Stream.Configuration.initial(), - }, - }); - const error = observe.json().from(json); - return error instanceof Error ? error : observe; - } - - static alias(): undefined { - return undefined; - } - - static initial(): IObserve { - return { - origin: Origin.Configuration.initial(), - parser: Parser.Configuration.initial(), - }; - } - - static validate(configuration: IObserve): Error | IObserve { - let error: Error | unknown = Origin.Configuration.validate(configuration.origin); - if (error instanceof Error) { - return error; - } - error = Parser.Configuration.validate(configuration.parser); - return error instanceof Error ? error : configuration; - } - - public readonly origin!: Origin.Configuration; - public readonly parser!: Parser.Configuration; - - /// Lock-state - /// Allows define a way to process observe data. - /// true - observe data can be used to observe (create a session); - /// false - observe data probably requires some addition configuration - /// for example settings of DLT or SomeIP - protected readonly lock: LockToken = new LockToken(false); - - protected link(): void { - (this as Mutable).origin = new Origin.Configuration(this.configuration.origin, { - watcher: this.watcher, - overwrite: (config: Origin.IConfiguration) => { - this.configuration.origin = config; - return this.configuration.origin; - }, - }); - (this as Mutable).parser = new Parser.Configuration(this.configuration.parser, { - watcher: this.watcher, - overwrite: (config: Parser.IConfiguration) => { - this.configuration.parser = config; - return this.configuration.parser; - }, - }); - } - - protected onOriginChange() { - this.parser.onOriginChange(this.origin); - } - - constructor(observe: IObserve) { - super(observe, undefined); - this.link(); - this.parser.onOriginChange(this.origin); - this.origin.watcher.subscribe(this.onOriginChange.bind(this)); - this.parser.watcher.subscribe(this.onOriginChange.bind(this)); - } - - public override destroy(): void { - super.destroy(); - this.parser !== undefined && this.parser.destroy(); - this.origin !== undefined && this.origin.destroy(); - } - - public override isSdeSupported(): boolean { - return this.origin.isSdeSupported(); - } - - public override overwrite(configuration: IObserve): void { - this.origin !== undefined && this.origin.destroy(); - this.parser !== undefined && this.parser.destroy(); - super.overwrite(configuration); - this.link(); - } - - public getSupportedParsers(): Parser.Reference[] { - return this.origin.getSupportedParsers(); - } - - public clone(): Observe { - const cloned = new Observe(this.sterilized()); - // Drop alias to prevent multiple observing entries with same UUID - cloned.origin.set().alias(); - return cloned; - } - - public locker(): { - lock(): Observe; - unlock(): Observe; - // Lock configuration if it's possible to lock - guess(): Observe; - is(): boolean; - } { - return { - lock: (): Observe => { - this.lock.lock(); - return this; - }, - unlock: (): Observe => { - this.lock.unlock(); - return this; - }, - guess: (): Observe => { - if (this.parser.instance instanceof Parser.Text.Configuration) { - this.lock.lock(); - } - return this; - }, - is: (): boolean => { - return this.lock.isLocked(); - }, - }; - } - - public isConfigurable(): boolean { - const map: any = getCompatibilityMod().Configurable; - const nature = this.origin.nature().alias(); - const parser = this.parser.alias(); - if (typeof map[nature] === 'boolean') { - return map[nature]; - } else if (typeof map[nature][parser] === 'boolean') { - return map[nature][parser]; - } - throw new Error( - `Parser "${parser}" and origin "${nature}" don't have description in Compatibility.Configurable table`, - ); - } - - public override storable(): IObserve { - return { - origin: this.origin.storable(), - parser: this.parser.storable(), - }; - } - - public override hash(): number { - return this.origin.hash() + this.parser.hash(); - } - - public signature(): string { - return `${this.origin.hash()}${this.parser.hash()}`; - } -} diff --git a/application/platform/types/observe/origin/concat.ts b/application/platform/types/observe/origin/concat.ts deleted file mode 100644 index 8e94cf386..000000000 --- a/application/platform/types/observe/origin/concat.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { error } from '../../../log/utils'; -import { Configuration as Base, ConfigurationStaticDesc } from '../configuration'; -import { OriginDetails, IOriginDetails, IList, Job, IJob, OriginType } from '../description'; -import { Configuration as ConfigurationFile } from './file'; -import { Context, SourceUuid, IConfiguration as IOriginConfiguration } from './index'; -import { getParentFolder } from '../../files'; -import { Statics } from '../../../env/decorators'; -import { unique } from '../../../env/sequence'; - -import * as Types from '../types'; -import * as Parser from '../parser'; -import * as Sde from '../sde'; -import * as str from '../../../env/str'; - -export type IConfiguration = [SourceUuid, Types.File.FileType, Types.File.FileName][]; - -@Statics>() -export class Configuration - extends Base - implements OriginDetails, Sde.Support, Parser.Support, Job -{ - static desc(): IList { - return { - major: `Files`, - minor: 'Local Files', - icon: 'files', - }; - } - - static alias(): Context { - return Context.Concat; - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - try { - if (!(configuration instanceof Array) || configuration.length === 0) { - throw new Error( - `Source "${Context.Concat}" should be represented as a not empty array.`, - ); - } else { - configuration.forEach((file) => { - // If file settings are not correct it will throw an error - new ConfigurationFile(file, undefined); - }); - } - return configuration; - } catch (e) { - return new Error(error(e)); - } - } - - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return []; - } - - static inited(): boolean { - return true; - } - - protected defaultFileType: Types.File.FileType = Types.File.FileType.Text; - - public source(): string | undefined { - return undefined; - } - - public set(): { - files(files: string[]): Configuration; - defaults(type: Types.File.FileType): Configuration; - push(filename: string, type: Types.File.FileType): Configuration; - remove(filename: string): Configuration; - alias(alias?: string): Configuration; - } { - return { - files: (files: string[]): Configuration => { - this.configuration.push( - ...(files.map((filename: string) => { - return [unique(), this.defaultFileType, filename]; - }) as IConfiguration), - ); - return this; - }, - defaults: (type: Types.File.FileType): Configuration => { - this.defaultFileType = type; - return this; - }, - push: (filename: string, type: Types.File.FileType): Configuration => { - this.configuration.push([unique(), type, filename]); - return this; - }, - remove: (filename: string): Configuration => { - const index = this.configuration.findIndex((def) => def[2] === filename); - if (index !== -1) { - this.configuration.splice(index, 1); - } - return this; - }, - alias: (alias?: string): Configuration => { - this.configuration.forEach((file) => { - file[0] = alias === undefined ? unique() : alias; - }); - return this; - }, - }; - } - - public files(): string[] { - return this.configuration.map((c) => c[2]); - } - - public filetypes(): Types.File.FileType[] { - return this.configuration.map((c) => c[1]); - } - - public asFileOrigins(): IOriginConfiguration[] { - return this.configuration.map((c) => { - return { - [Context.File]: c, - }; - }); - } - - public desc(): IOriginDetails { - const first = this.configuration[0]; - return { - major: `Concating ${this.configuration.length} files`, - minor: first !== undefined ? getParentFolder(first[2]) : '', - icon: 'insert_drive_file', - type: OriginType.file, - action: 'Concat', - state: { - running: 'processing', - stopped: '', - }, - }; - } - - public asJob(): IJob { - return { - name: 'concating', - desc: `concating ${this.configuration.length} files`, - icon: 'insert_drive_file', - }; - } - - public getSupportedParsers(): Parser.Reference[] { - if (this.configuration.length === 0) { - // Returns default - return [ - Parser.Dlt.Configuration, - Parser.SomeIp.Configuration, - Parser.Text.Configuration, - Parser.Plugin.Configuration, - ]; - } - switch (this.configuration[0][1]) { - case Types.File.FileType.Binary: - return [Parser.Dlt.Configuration, Parser.SomeIp.Configuration]; - case Types.File.FileType.PcapNG: - return [Parser.Dlt.Configuration, Parser.SomeIp.Configuration]; - case Types.File.FileType.PcapLegacy: - return [Parser.Dlt.Configuration, Parser.SomeIp.Configuration]; - case Types.File.FileType.Text: - return [Parser.Text.Configuration]; - case Types.File.FileType.ParserPlugin: - return [Parser.Plugin.Configuration]; - } - } - - public override hash(): number { - return str.hash(`${this.files().join(';')};${this.filetypes().join(';')}`); - } -} diff --git a/application/platform/types/observe/origin/file.ts b/application/platform/types/observe/origin/file.ts deleted file mode 100644 index 50d31eeb3..000000000 --- a/application/platform/types/observe/origin/file.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { error } from '../../../log/utils'; -import { Configuration as Base, ConfigurationStaticDesc } from '../configuration'; -import { Context, SourceUuid } from './index'; -import { OriginDetails, IOriginDetails, IList, Job, IJob, OriginType } from '../description'; -import { getFileName, getParentFolder } from '../../files'; -import { Statics } from '../../../env/decorators'; -import { unique } from '../../../env/sequence'; - -import * as Types from '../types'; -import * as Parser from '../parser'; -import * as Sde from '../sde'; -import * as str from '../../../env/str'; - -export type IConfiguration = [SourceUuid, Types.File.FileType, Types.File.FileName]; - -@Statics>() -export class Configuration - extends Base - implements OriginDetails, Sde.Support, Parser.Support, Job -{ - static desc(): IList { - return { - major: `File`, - minor: 'Local File', - icon: 'file', - }; - } - - static alias(): Context { - return Context.File; - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - try { - if (configuration instanceof Array && configuration.length === 3) { - str.asNotEmptyString( - configuration[0], - `SourceUuid isn't found: ${configuration[0]}`, - ); - str.asNotEmptyString( - configuration[2], - `Origin.FileName isn't found: ${configuration[2]}`, - ); - if (Types.File.getFileTypeFrom(configuration[1]) instanceof Error) { - throw new Error(`Invalid Origin.FileType: ${configuration[1]}`); - } - } else { - throw new Error( - `Source "${Context.File}" should be represented as an array, len = 3.`, - ); - } - return configuration; - } catch (e) { - return new Error(error(e)); - } - } - - static inited(): boolean { - return true; - } - - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return [unique(), Types.File.FileType.Text, '']; - } - - public source(): string | undefined { - return this.configuration[0]; - } - - public set(): { - filename(filename: string): Configuration; - type(type: Types.File.FileType): Configuration; - alias(alias?: string): Configuration; - } { - return { - filename: (filename: string): Configuration => { - this.configuration[2] = filename; - return this; - }, - type: (type: Types.File.FileType): Configuration => { - this.configuration[1] = type; - return this; - }, - alias: (alias: string): Configuration => { - this.configuration[0] = alias === undefined ? unique() : alias; - return this; - }, - }; - } - - public get(): { - filename(): string; - type(): Types.File.FileType; - alias(): string; - } { - return { - filename: (): string => { - return this.configuration[2]; - }, - type: (): Types.File.FileType => { - return this.configuration[1]; - }, - alias: (): string => { - return this.configuration[0]; - }, - }; - } - - public filename(): string { - return this.configuration[2]; - } - - public filetype(): Types.File.FileType { - return this.configuration[1]; - } - - public desc(): IOriginDetails { - return { - major: getFileName(this.configuration[2]), - minor: getParentFolder(this.configuration[2]), - icon: 'insert_drive_file', - type: OriginType.file, - action: 'Open', - state: { - running: 'tail', - stopped: '', - }, - }; - } - - public asJob(): IJob { - return { - name: 'tail', - desc: getFileName(this.filename()), - icon: 'insert_drive_file', - }; - } - - public getSupportedParsers(): Parser.Reference[] { - switch (this.configuration[1]) { - case Types.File.FileType.Binary: - return [ - Parser.Dlt.Configuration, - Parser.SomeIp.Configuration, - Parser.Plugin.Configuration, - ]; - case Types.File.FileType.PcapNG: - return [ - Parser.Dlt.Configuration, - Parser.SomeIp.Configuration, - Parser.Plugin.Configuration, - ]; - case Types.File.FileType.PcapLegacy: - return [ - Parser.Dlt.Configuration, - Parser.SomeIp.Configuration, - Parser.Plugin.Configuration, - ]; - case Types.File.FileType.Text: - return [Parser.Text.Configuration]; - case Types.File.FileType.ParserPlugin: - return [Parser.Plugin.Configuration]; - } - } - - public override hash(): number { - return str.hash(`${this.filename()};${this.filetype()}`); - } -} diff --git a/application/platform/types/observe/origin/index.ts b/application/platform/types/observe/origin/index.ts deleted file mode 100644 index 44794c365..000000000 --- a/application/platform/types/observe/origin/index.ts +++ /dev/null @@ -1,258 +0,0 @@ -import { Configuration as Base, ConfigurationStatic, Linked } from '../configuration'; -import { Statics } from '../../../env/decorators'; -import { Mutable } from '../../unity/mutable'; -import { Alias } from '../../env/types'; -import { OriginDetails, IOriginDetails, Job, IJob } from '../description'; - -export * as File from './file'; -export * as Concat from './concat'; -export * as Stream from './stream'; -export * as Plugin from './plugin'; - -import * as File from './file'; -import * as Concat from './concat'; -import * as Stream from './stream'; -import * as Plugin from './plugin'; -import * as Parser from '../parser'; -import * as Sde from '../sde'; - -//TODO: Plugin byte source integration will be postponed. -export enum Context { - File = 'File', - Concat = 'Concat', - Stream = 'Stream', - Plugin = 'Plugin', -} - -export type SourceUuid = string; - -export interface OnChange { - onOriginChange(origin: Configuration): void; -} - -export interface IConfiguration { - [Context.File]?: File.IConfiguration; - [Context.Concat]?: Concat.IConfiguration; - [Context.Stream]?: Stream.IConfiguration; - [Context.Plugin]?: Plugin.IConfiguration; -} - -export const REGISTER = { - [Context.File]: File.Configuration, - [Context.Concat]: Concat.Configuration, - [Context.Stream]: Stream.Configuration, - [Context.Plugin]: Plugin.Configuration, -}; - -export const DEFAULT = File.Configuration; - -export type Declaration = - | File.Configuration - | Concat.Configuration - | Stream.Configuration - | Plugin.Configuration; - -export type OriginNature = - | File.Configuration - | Concat.Configuration - | Stream.Stream.Declaration - | Plugin.Configuration; - -@Statics>() -export class Configuration - extends Base - implements Parser.Support, Sde.Support, Job, OriginDetails -{ - static alias(): undefined { - return undefined; - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - if ( - Object.keys(REGISTER) - .map((k) => configuration[k as Context]) - .filter((v) => v !== undefined).length === 0 - ) { - return new Error(`Origin isn't defined`); - } - let error: Error | undefined; - Object.keys(REGISTER).forEach((key) => { - if (error instanceof Error) { - return; - } - const config = configuration[key as Context]; - if (config === undefined) { - return; - } - const err = REGISTER[key as Context].validate(config as any); - if (err instanceof Error) { - error = err; - } else { - error = undefined; - } - }); - return error instanceof Error ? error : configuration; - } - - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return { - [DEFAULT.alias()]: DEFAULT.initial(), - }; - } - - protected getContextKey(): Context | undefined { - let found: Context | undefined; - Object.keys(REGISTER).forEach((key) => { - if (found !== undefined) { - return; - } - if (this.configuration[key as Context] === undefined) { - return; - } - found = key as Context; - }); - return found; - } - - protected setInstance(): Configuration { - const context = this.getContextKey(); - if (context === undefined) { - throw new Error(`Configuration of stream doesn't have definition of known context.`); - } - if (this.instance !== undefined && this.instance.alias() === context) { - this.instance.setRef(this.configuration[context]); - return this; - } - this.instance !== undefined && this.instance.destroy(); - (this as Mutable).instance = new REGISTER[context]( - this.configuration[context] as any, - { - watcher: this.watcher, - overwrite: (config: IConfiguration) => { - this.overwrite(config); - return this.configuration[context] as any; - }, - }, - ); - return this; - } - - public readonly instance!: Declaration; - - constructor(configuration: IConfiguration, linked: Linked | undefined) { - super(configuration, linked); - this.register( - this.watcher.subscribe(() => { - if (this.overwriting) { - return; - } - this.setInstance(); - }), - ); - this.setInstance(); - } - - public override destroy(): void { - super.destroy(); - this.instance !== undefined && this.instance.destroy(); - } - - public source(): string | undefined { - return this.instance.source(); - } - - public files(): string[] | string | undefined { - if (this.instance instanceof File.Configuration) { - return this.instance.filename(); - } else if (this.instance instanceof Concat.Configuration) { - return this.instance.files(); - } else { - return undefined; - } - } - - public change(origin: Declaration): void { - this.overwrite({ [origin.alias()]: origin.configuration }); - } - - public desc(): IOriginDetails { - return this.instance.desc(); - } - - public title(): string { - const desc = this.desc().major; - if (desc.trim() === '') { - if (this.instance instanceof File.Configuration) { - return File.Configuration.desc().major; - } else if (this.instance instanceof Concat.Configuration) { - return Concat.Configuration.desc().major; - } else if (this.instance instanceof Stream.Configuration) { - return Stream.Configuration.desc().major; - } else { - throw new Error(`Origin type isn't supported yet`); - } - } else { - return desc; - } - } - - public asJob(): IJob { - return this.instance.asJob(); - } - - public override isSdeSupported(): boolean { - return this.instance.isSdeSupported(); - } - - public getSupportedParsers(): Parser.Reference[] { - return this.instance.getSupportedParsers(); - } - - public as( - Ref: { new (...args: any[]): Declaration | Stream.Stream.Declaration } & Alias, - ): T | undefined { - if (this.instance.alias() === Ref.alias()) { - return this.instance as T; - } - if (typeof (this.instance as any).as === 'function') { - return (this.instance as any).as(Ref); - } - return undefined; - } - - public nature(): OriginNature { - if (this.instance instanceof Stream.Configuration) { - return this.instance.instance.instance; - } else { - return this.instance; - } - } - - public getNatureAlias(): Context | Stream.Stream.Source { - if (this.instance instanceof Stream.Configuration) { - return this.instance.instance.instance.alias(); - } else { - return this.instance.alias(); - } - } - - public set(): { - alias(alias?: string): void; - } { - return { - // Change signature of source - alias: (alias?: string): void => { - this.instance.set().alias(alias); - }, - }; - } - - public override storable(): IConfiguration { - return { [this.instance.alias()]: this.instance.storable() }; - } - - public override hash(): number { - return this.instance.hash(); - } -} diff --git a/application/platform/types/observe/origin/plugin.ts b/application/platform/types/observe/origin/plugin.ts deleted file mode 100644 index e9f260459..000000000 --- a/application/platform/types/observe/origin/plugin.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { Configuration as Base, ConfigurationStaticDesc } from '../configuration'; -import { OriginDetails, Job, IOriginDetails, IJob, OriginType, IList } from '../description'; -import { Context, SourceUuid } from './index'; -import { Statics } from '../../../env/decorators'; -import { unique } from '../../../env/sequence'; -import { error } from '../../../log/utils'; - -import * as Types from '../types'; -import * as Sde from '../sde'; -import * as Parser from '../parser'; -import * as str from '../../../env/str'; - -export type IConfiguration = [SourceUuid, Types.Plugin.PluginDirPath]; -const CONFIG_LEN = 2 as const; - -@Statics>() -export class Configuration - extends Base - implements OriginDetails, Sde.Support, Parser.Support, Job -{ - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return [unique(), '']; - } - - static alias(): Context { - return Context.Plugin; - } - - /// Origin (source) plugin will describe itself in run-time. fields `major` will be replaced - // with plugin name; `minor` with an addition data. - /// It will work simular to `WrappedParserRef` (application/client/src/app/ui/tabs/observe/state.ts) - static desc(): IList { - return { - major: '', - minor: '', - icon: undefined, - }; - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - try { - if (configuration instanceof Array && configuration.length === CONFIG_LEN) { - str.asNotEmptyString( - configuration[0], - `SourceUuid isn't found: ${configuration[0]}`, - ); - str.asNotEmptyString( - configuration[1], - `Pluging Directory Path isn't found: ${configuration[1]}`, - ); - } else { - throw new Error( - `Source "${Context.Plugin}" should be represented as an array, len = ${CONFIG_LEN}`, - ); - } - - return configuration; - } catch (e) { - return new Error(error(e)); - } - } - - static inited(): boolean { - return Configuration.desc().major.trim() !== ''; - } - - public source(): string | undefined { - return this.configuration[0]; - } - - public set(): { - alias(alias?: string): void; - pluginDir(dir: string): void; - } { - return { - alias: (alias?: string): void => { - this.configuration[0] = alias === undefined ? unique() : alias; - }, - pluginDir: (dir: string): void => { - this.configuration[1] = dir; - }, - }; - } - - public override hash(): number { - return str.hash(this.dirPath()); - } - - public dirPath(): Types.Plugin.PluginDirPath { - return this.configuration[1]; - } - - public getSupportedParsers(): Parser.Reference[] { - // Returns all - return [ - Parser.Dlt.Configuration, - Parser.SomeIp.Configuration, - Parser.Text.Configuration, - Parser.Plugin.Configuration, - ]; - } - public asJob(): IJob { - return { - name: 'plugin source', - desc: 'plugin source TODO', - icon: undefined, - }; - } - public desc(): IOriginDetails { - return { - major: 'Plugin source instance major TODO', - minor: 'Plugin source instance minor TODO', - icon: undefined, - type: OriginType.plugin, - action: 'Load TODO', - state: { - running: 'load TODO', - stopped: 'TODO', - }, - }; - } -} diff --git a/application/platform/types/observe/origin/stream.ts b/application/platform/types/observe/origin/stream.ts deleted file mode 100644 index 6634c1573..000000000 --- a/application/platform/types/observe/origin/stream.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { error } from '../../../log/utils'; -import { Configuration as Base, ConfigurationStatic, Linked } from '../configuration'; -import { Context, SourceUuid } from './index'; -import { OriginDetails, IOriginDetails, IList, Job, IJob } from '../description'; -import { Statics } from '../../../env/decorators'; -import { unique } from '../../../env/sequence'; -import { Alias } from '../../env/types'; -import { Mutable } from '../../unity/mutable'; - -import * as str from '../../../env/str'; -import * as Stream from './stream/index'; -import * as Parser from '../parser/index'; -import * as Sde from '../sde'; - -export * as str from '../../../env/str'; -export * as Stream from './stream/index'; -export * as Parser from '../parser/index'; -export * as Sde from '../sde'; - -export type IConfiguration = [SourceUuid, Stream.IConfiguration]; - -@Statics>() -export class Configuration - extends Base - implements OriginDetails, Sde.Support, Parser.Support, Job -{ - static desc(): IList { - return { - major: `Stream`, - minor: 'Streaming', - icon: 'files', - }; - } - - static alias(): Context { - return Context.Stream; - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - try { - if (configuration instanceof Array && configuration.length === 2) { - str.asNotEmptyString( - configuration[0], - `SourceUuid isn't found: ${configuration[0]}`, - ); - const error = Stream.Configuration.validate(configuration[1]); - return error instanceof Error ? error : configuration; - } else { - throw new Error( - `Source "${Context.Stream}" should be represented as an array, len = 2.`, - ); - } - } catch (e) { - return new Error(error(e)); - } - } - - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return [unique(), Stream.Configuration.initial()]; - } - - protected setInstance(): Configuration { - if (this.instance !== undefined) { - this.instance.setRef(this.configuration[1]); - return this; - } else { - const instance = new Stream.Configuration(this.configuration[1], { - watcher: this.watcher, - overwrite: (config: Stream.IConfiguration) => { - this.configuration[1] = config; - return this.configuration[1]; - }, - }); - if (instance instanceof Error) { - throw instance; - } - (this as Mutable).instance = instance; - } - return this; - } - - public readonly instance!: Stream.Configuration; - - constructor(configuration: IConfiguration, linked: Linked | undefined) { - super(configuration, linked); - this.register( - this.watcher.subscribe(() => { - this.setInstance(); - }), - ); - this.setInstance(); - } - - public source(): string | undefined { - return this.configuration[0]; - } - - public override destroy(): void { - super.destroy(); - this.instance !== undefined && this.instance.destroy(); - } - - public change(stream: Stream.Declaration): void { - this.instance.change().byDeclaration(stream); - this.configuration[1] = this.instance.configuration; - } - - public desc(): IOriginDetails { - return this.instance.desc(); - } - - public asJob(): IJob { - return this.instance.asJob(); - } - - public override isSdeSupported(): boolean { - return this.instance.isSdeSupported(); - } - - public getSupportedParsers(): Parser.Reference[] { - return this.instance.getSupportedParsers(); - } - - public set(): { - alias(alias?: string): void; - } { - return { - alias: (alias?: string): void => { - this.configuration[0] = alias === undefined ? unique() : alias; - }, - }; - } - - public as( - Ref: { new (...args: any[]): Stream.Declaration } & Alias, - ): T | undefined { - return this.instance.instance.alias() === Ref.alias() - ? (this.instance.instance as T) - : undefined; - } - - public override storable(): IConfiguration { - return [this.configuration[0], this.instance.storable()]; - } - - public override hash(): number { - return this.instance.hash(); - } -} diff --git a/application/platform/types/observe/origin/stream/index.ts b/application/platform/types/observe/origin/stream/index.ts deleted file mode 100644 index 4541c4579..000000000 --- a/application/platform/types/observe/origin/stream/index.ts +++ /dev/null @@ -1,255 +0,0 @@ -import { - Configuration as Base, - ConfigurationStatic, - ReferenceDesc, - Linked, -} from '../../configuration'; -import { OriginDetails, IOriginDetails, Job, IJob } from '../../description'; -import { Statics } from '../../../../env/decorators'; -import { Mutable } from '../../../unity/mutable'; -import { Alias } from '../../../env/types'; - -import * as Process from './process'; -import * as Serial from './serial'; -import * as TCP from './tcp'; -import * as UDP from './udp'; -import * as Parser from '../../parser'; -import * as Sde from '../../sde'; - -export * as Process from './process'; -export * as Serial from './serial'; -export * as TCP from './tcp'; -export * as UDP from './udp'; - -export type Reference = - | ReferenceDesc - | ReferenceDesc - | ReferenceDesc - | ReferenceDesc; - -export enum Source { - TCP = 'TCP', - UDP = 'UDP', - Serial = 'Serial', - Process = 'Process', -} - -export type IDeclaration = - | Serial.IConfiguration - | Process.IConfiguration - | TCP.IConfiguration - | UDP.IConfiguration; - -export type Declaration = - | Serial.Configuration - | Process.Configuration - | TCP.Configuration - | UDP.Configuration; - -export interface IConfiguration { - [Source.Serial]?: Serial.IConfiguration; - [Source.Process]?: Process.IConfiguration; - [Source.TCP]?: TCP.IConfiguration; - [Source.UDP]?: UDP.IConfiguration; -} - -export const REGISTER: { - [key: string]: Reference; -} = { - [Source.Process]: Process.Configuration, - [Source.Serial]: Serial.Configuration, - [Source.TCP]: TCP.Configuration, - [Source.UDP]: UDP.Configuration, -}; - -export function getAllRefs(): Reference[] { - return Object.keys(REGISTER).map((key) => REGISTER[key]); -} - -export abstract class Support { - public abstract getSupportedStream(): Reference[]; -} - -export const DEFAULT = TCP.Configuration; - -export function getByAlias(alias: Source, configuration?: IDeclaration): Declaration { - const Ref: Reference = REGISTER[alias]; - if (Ref === undefined) { - throw new Error(`Unknown stream: ${alias}`); - } - return new Ref(configuration === undefined ? Ref.initial() : configuration, undefined); -} - -export function getAliasByConfiguration(configuration: IConfiguration): Source { - let detected: Source | undefined; - Object.keys(configuration).forEach((key) => { - if (REGISTER[key as Source] !== undefined) { - if (detected !== undefined) { - throw new Error( - `Configuration has a multiple source defined: ${JSON.stringify(configuration)}`, - ); - } - detected = key as Source; - } - }); - if (detected === undefined) { - throw new Error( - `Configuration doesn't have any known source: ${JSON.stringify(configuration)}`, - ); - } - return detected; -} - -@Statics>() -export class Configuration - extends Base - implements OriginDetails, Sde.Support, Parser.Support, Job -{ - static alias(): Source { - //TODO: alias should be defined for holders. Same for Parser as holder of parsers - return Source.Process; - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - if ( - Object.keys(REGISTER) - .map((k) => configuration[k as Source]) - .filter((v) => v !== undefined).length === 0 - ) { - return new Error(`Stream transport isn't defined`); - } - let error: Error | undefined; - Object.keys(REGISTER).forEach((key) => { - if (error instanceof Error) { - return; - } - const config: any = configuration[key as Source]; - if (config === undefined) { - return; - } - const err = REGISTER[key as Source].validate(config); - if (err instanceof Error) { - error = err; - } else { - error = undefined; - } - }); - return error instanceof Error ? error : configuration; - } - - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return { - [DEFAULT.alias()]: DEFAULT.initial(), - }; - } - - protected getSourceKey(): Source | undefined { - let found: Source | undefined; - Object.keys(REGISTER).forEach((key) => { - if (found !== undefined) { - return; - } - if (this.configuration[key as Source] === undefined) { - return; - } - found = key as Source; - }); - return found; - } - - protected setInstance(): Configuration { - const source = this.getSourceKey(); - if (source === undefined) { - throw new Error(`Configuration of stream doesn't have definition of known source.`); - } - if (this.configuration[source] === undefined) { - throw new Error(`No source is defined in stream configuration`); - } - if (this.instance !== undefined && this.instance.alias() === source) { - this.instance.setRef(this.configuration[source]); - return this; - } - this.instance !== undefined && this.instance.destroy(); - (this as Mutable).instance = new REGISTER[source]( - this.configuration[source], - { - watcher: this.watcher, - overwrite: (config: IConfiguration) => { - this.overwrite({ [source]: config }); - return this.configuration[source]; - }, - }, - ); - return this; - } - - public readonly instance!: Declaration; - - constructor(configuration: IConfiguration, linked: Linked | undefined) { - super(configuration, linked); - this.register( - this.watcher.subscribe(() => { - this.setInstance(); - }), - ); - this.setInstance(); - } - - public override destroy(): void { - super.destroy(); - this.instance !== undefined && this.instance.destroy(); - } - - public change(): { - byConfiguration(configuration: IConfiguration): void; - byDeclaration(declaration: Declaration): void; - byReference(reference: Reference): void; - } { - return { - byConfiguration: (configuration: IConfiguration): void => { - this.overwrite(configuration); - }, - byDeclaration: (stream: Declaration): void => { - this.overwrite({ [stream.alias()]: stream.configuration }); - }, - byReference: (Ref: Reference): void => { - this.overwrite({ [Ref.alias()]: new Ref(Ref.initial(), this.linked) }); - }, - }; - } - - public desc(): IOriginDetails { - return this.instance.desc(); - } - - public asJob(): IJob { - return this.instance.asJob(); - } - - public override isSdeSupported(): boolean { - return this.instance.isSdeSupported(); - } - - public getSupportedParsers(): Parser.Reference[] { - return this.instance.getSupportedParsers(); - } - - public as(Ref: { new (...args: any[]): Declaration } & Alias): T | undefined { - if (this.instance.alias() === Ref.alias()) { - return this.instance as T; - } - if (typeof (this.instance as any).as === 'function') { - return (this.instance as any).as(Ref); - } - return undefined; - } - - public override storable(): IConfiguration { - return { [this.instance.alias()]: this.instance.storable() }; - } - - public override hash(): number { - return this.instance.hash(); - } -} diff --git a/application/platform/types/observe/origin/stream/process/index.ts b/application/platform/types/observe/origin/stream/process/index.ts deleted file mode 100644 index 1d895b453..000000000 --- a/application/platform/types/observe/origin/stream/process/index.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { error } from '../../../../../log/utils'; -import { Source } from '../index'; -import { Configuration as Base, ConfigurationStaticDesc } from '../../../configuration'; -import { OriginDetails, IOriginDetails, IList, Job, IJob, OriginType } from '../../../description'; -import { Statics } from '../../../../../env/decorators'; - -import * as obj from '../../../../../env/obj'; -import * as Parser from '../../../parser'; -import * as Sde from '../../../sde'; -import * as str from '../../../../../env/str'; - -export interface IConfiguration { - command: string; - cwd: string; - envs: { [key: string]: string }; -} - -@Statics>() -export class Configuration - extends Base - implements OriginDetails, Sde.Support, Job -{ - public MARKER = 'application/platform/types/observe/origin/stream/process/index.ts'; - - static desc(): IList { - return { - major: `Terminal`, - minor: 'Executing Terminal Command', - icon: 'web_asset', - }; - } - - static alias(): Source { - return Source.Process; - } - - static sterilizeEnvVars(envs: { [key: string]: any }): { [key: string]: string } { - Object.keys(envs).forEach((key: string) => { - if (typeof envs[key] === 'string') { - return; - } - if (typeof envs[key].toString === 'function') { - envs[key] = envs[key].toString(); - } else { - envs[key] = JSON.stringify(envs[key]); - } - }); - return envs; - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - try { - obj.getAsNotEmptyString(configuration, 'command'); - obj.getAsString(configuration, 'cwd'); - obj.getAsObjWithPrimitives(configuration, 'envs'); - return configuration; - } catch (e) { - return new Error(error(e)); - } - } - - static inited(): boolean { - return true; - } - - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return { - command: '', - cwd: '', - envs: {}, - }; - } - - public desc(): IOriginDetails { - return { - major: `${this.configuration.command}`, - minor: this.configuration.cwd === '' ? 'no defined cwd' : this.configuration.cwd, - icon: 'web_asset', - action: 'Execute', - type: OriginType.command, - state: { - running: 'spawning', - stopped: '', - }, - }; - } - - public asJob(): IJob { - return { - name: `${this.configuration.command}`, - desc: `${this.configuration.command}`, - icon: 'web_asset', - }; - } - - public getSupportedParsers(): Parser.Reference[] { - return [Parser.Text.Configuration, Parser.Plugin.Configuration]; - } - - public override storable(): IConfiguration { - const sterilized = this.sterilized(); - return sterilized; - } - - public override hash(): number { - return str.hash(`${this.configuration.command};${this.configuration.cwd}`); - } -} diff --git a/application/platform/types/observe/origin/stream/serial/index.ts b/application/platform/types/observe/origin/stream/serial/index.ts deleted file mode 100644 index 78bc4aa60..000000000 --- a/application/platform/types/observe/origin/stream/serial/index.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { error } from '../../../../../log/utils'; -import { Source } from '../index'; -import { Configuration as Base, ConfigurationStaticDesc } from '../../../configuration'; -import { OriginDetails, IOriginDetails, IList, Job, IJob, OriginType } from '../../../description'; -import { Statics } from '../../../../../env/decorators'; - -import * as obj from '../../../../../env/obj'; -import * as Parser from '../../../parser'; -import * as Sde from '../../../sde'; -import * as str from '../../../../../env/str'; - -export interface IConfiguration { - path: string; - baud_rate: number; - data_bits: number; - flow_control: number; - parity: number; - stop_bits: number; - send_data_delay: number; - exclusive: boolean; -} - -@Statics>() -export class Configuration - extends Base - implements OriginDetails, Sde.Support, Job -{ - public MARKER = 'application/platform/types/observe/origin/stream/serial/index.ts'; - - static desc(): IList { - return { - major: `Serial Port`, - minor: 'Connection to Serial Port', - icon: 'import_export', - }; - } - - static alias(): Source { - return Source.Serial; - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - try { - obj.getAsNotEmptyString(configuration, 'path'); - obj.getAsValidNumber(configuration, 'baud_rate'); - obj.getAsValidNumber(configuration, 'data_bits'); - obj.getAsValidNumber(configuration, 'flow_control'); - obj.getAsValidNumber(configuration, 'parity'); - obj.getAsValidNumber(configuration, 'stop_bits'); - return configuration; - } catch (e) { - return new Error(error(e)); - } - } - - static inited(): boolean { - return true; - } - - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return { - baud_rate: 115200, - data_bits: 8, - flow_control: 0, - parity: 0, - path: '', - stop_bits: 1, - send_data_delay: 0, - exclusive: true, - }; - } - - public desc(): IOriginDetails { - return { - major: this.configuration.path, - minor: `Baud Rate: ${this.configuration.baud_rate}`, - icon: 'import_export', - action: 'Connect', - type: OriginType.serial, - state: { - running: 'listening', - stopped: '', - }, - }; - } - - public asJob(): IJob { - return { - name: `Serial: ${this.configuration.path}`, - desc: `Baud Rate: ${this.configuration.baud_rate}`, - icon: 'import_export', - }; - } - - public getSupportedParsers(): Parser.Reference[] { - return [Parser.Text.Configuration, Parser.Plugin.Configuration]; - } - - public override hash(): number { - return str.hash( - `${this.configuration.path};${this.configuration.baud_rate};${this.configuration.data_bits};${this.configuration.flow_control};${this.configuration.parity};${this.configuration.stop_bits}`, - ); - } -} diff --git a/application/platform/types/observe/origin/stream/tcp/index.ts b/application/platform/types/observe/origin/stream/tcp/index.ts deleted file mode 100644 index 58c5166a8..000000000 --- a/application/platform/types/observe/origin/stream/tcp/index.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Source } from '../index'; -import { Configuration as Base, ConfigurationStaticDesc } from '../../../configuration'; -import { OriginDetails, IOriginDetails, IList, Job, IJob, OriginType } from '../../../description'; -import { Statics } from '../../../../../env/decorators'; - -import * as Parser from '../../../parser'; -import * as Sde from '../../../sde'; -import * as Ip from '../../../../../env/ipaddr'; -import * as str from '../../../../../env/str'; - -export interface IConfiguration { - bind_addr: string; -} - -@Statics>() -export class Configuration - extends Base - implements OriginDetails, Sde.Support, Job -{ - public MARKER = 'application/platform/types/observe/origin/stream/tcp/index.ts'; - - static desc(): IList { - return { - major: `TCP`, - minor: 'TCP Connection', - icon: 'network_wifi_3_bar', - }; - } - - static alias(): Source { - return Source.TCP; - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - return Ip.anyIPAddr(configuration.bind_addr) - ? configuration - : new Error(`Invalid IP address`); - } - - static inited(): boolean { - return true; - } - - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return { - bind_addr: '', - }; - } - - public desc(): IOriginDetails { - return { - major: this.configuration.bind_addr, - minor: '', - icon: 'network_wifi_3_bar', - type: OriginType.net, - action: 'Connect', - state: { - running: 'listening', - stopped: '', - }, - }; - } - - public asJob(): IJob { - return { - name: `TCP: ${this.configuration.bind_addr}`, - desc: `Connecting to ${this.configuration.bind_addr} via TCP`, - icon: 'network_wifi_3_bar', - }; - } - - public getSupportedParsers(): Parser.Reference[] { - return [Parser.Dlt.Configuration, Parser.Plugin.Configuration]; - } - - public override hash(): number { - return str.hash(`${this.configuration.bind_addr}`); - } -} diff --git a/application/platform/types/observe/origin/stream/udp/index.ts b/application/platform/types/observe/origin/stream/udp/index.ts deleted file mode 100644 index 61a2f5124..000000000 --- a/application/platform/types/observe/origin/stream/udp/index.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { error } from '../../../../../log/utils'; -import { Source } from '../index'; -import { Configuration as Base, ConfigurationStaticDesc } from '../../../configuration'; -import { OriginDetails, IOriginDetails, IList, Job, IJob, OriginType } from '../../../description'; -import { Statics } from '../../../../../env/decorators'; - -import * as obj from '../../../../../env/obj'; -import * as Parser from '../../../parser'; -import * as Sde from '../../../sde'; -import * as Ip from '../../../../../env/ipaddr'; -import * as str from '../../../../../env/str'; - -export interface Multicast { - multiaddr: string; - interface: string | undefined; -} - -export interface IConfiguration { - bind_addr: string; - multicast: Multicast[]; -} - -@Statics>() -export class Configuration - extends Base - implements OriginDetails, Sde.Support, Job -{ - public MARKER = 'application/platform/types/observe/origin/stream/udp/index.ts'; - - static desc(): IList { - return { - major: `UDP`, - minor: 'UDP Connection', - icon: 'network_wifi_3_bar', - }; - } - - static alias(): Source { - return Source.UDP; - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - try { - if (!Ip.anyIPAddr(configuration.bind_addr)) { - return new Error(`Invalid binding address`); - } - obj.getAsArray(configuration, 'multicast'); - return configuration.multicast - .map((multicast: Multicast) => { - return ( - Ip.anyIPAddr(multicast.multiaddr) && - (Ip.isValidIPv4(multicast.interface) || Ip.isValidIPv6(multicast.interface)) - ); - }) - .filter((r) => !r).length === 0 - ? configuration - : new Error(`Invalid multicast definition`); - } catch (e) { - return new Error(error(e)); - } - } - - static inited(): boolean { - return true; - } - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return { - bind_addr: '', - multicast: [], - }; - } - - public desc(): IOriginDetails { - return { - major: this.configuration.bind_addr, - minor: - this.configuration.multicast.length === 0 - ? '' - : this.configuration.multicast.map((m) => m.multiaddr).join(', '), - action: 'Connect', - icon: 'network_wifi_3_bar', - type: OriginType.net, - state: { - running: 'listening', - stopped: '', - }, - }; - } - - public asJob(): IJob { - return { - name: `UDP: ${this.configuration.bind_addr}`, - desc: - this.configuration.multicast.length === 0 - ? `Connecting to ${this.configuration.bind_addr} via UDP (no multicasts)` - : this.configuration.multicast.map((m) => m.multiaddr).join(', '), - icon: 'network_wifi_3_bar', - }; - } - - public getSupportedParsers(): Parser.Reference[] { - return [Parser.Dlt.Configuration, Parser.SomeIp.Configuration, Parser.Plugin.Configuration]; - } - - public override hash(): number { - return str.hash( - `${this.configuration.bind_addr};${this.configuration.multicast - .map((m) => `${m.multiaddr};${m.interface}`) - .join(';')}`, - ); - } -} diff --git a/application/platform/types/observe/parser/dlt/index.ts b/application/platform/types/observe/parser/dlt/index.ts deleted file mode 100644 index 4eddc0d22..000000000 --- a/application/platform/types/observe/parser/dlt/index.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { error } from '../../../../log/utils'; -import { Protocol } from '../index'; -import { Configuration as Base, ConfigurationStaticDesc } from '../../configuration'; -import { Statics } from '../../../../env/decorators'; -import { List, IList } from '../../description'; - -import * as Stream from '../../origin/stream/index'; -import * as Files from '../../types/file'; -import * as obj from '../../../../env/obj'; -import * as Origin from '../../origin/index'; -import * as str from '../../../../env/str'; - -export function getLogLevelName(level: number): string { - const name = (DltLogLevelNames as Record)[level]; - return name === undefined ? 'unknown' : name; -} - -export const DltLogLevelNames = { - 1: 'Fatal', - 2: 'Error', - 3: 'Warn', - 4: 'Info', - 5: 'Debug', - 6: 'Verbose', -}; - -export enum LogLevel { - Fatal = 1, - Error = 2, - Warn = 3, - Info = 4, - Debug = 5, - Verbose = 6, -} - -export interface IFilters { - min_log_level: LogLevel | undefined; - app_ids: string[] | undefined; - ecu_ids: string[] | undefined; - context_ids: string[] | undefined; - app_id_count: number; - context_id_count: number; -} - -export interface IConfiguration { - filter_config: IFilters | undefined; - fibex_file_paths: string[] | undefined; - with_storage_header: boolean; - tz: string | undefined; -} - -@Statics>() -export class Configuration - extends Base - implements List, Stream.Support, Files.Support, Origin.OnChange -{ - static desc(): IList { - return { - major: 'DLT', - minor: 'Parsing DLT tracers', - icon: undefined, - }; - } - - static alias(): Protocol { - return Protocol.Dlt; - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - try { - obj.getAsBool(configuration, 'with_storage_header'); - obj.getAsNotEmptyStringsArrayOrUndefined(configuration, 'fibex_file_paths'); - obj.getAsObjOrUndefined(configuration, 'filter_config'); - obj.getAsNotEmptyStringOrAsUndefined(configuration, 'tz'); - const filter_config = configuration.filter_config; - if (filter_config !== undefined) { - obj.getAsValidNumber(filter_config, 'min_log_level'); - obj.getAsNotEmptyStringsArrayOrUndefined(filter_config, 'app_ids'); - obj.getAsNotEmptyStringsArrayOrUndefined(filter_config, 'ecu_ids'); - obj.getAsNotEmptyStringsArrayOrUndefined(filter_config, 'context_ids'); - obj.getAsValidNumber(filter_config, 'app_id_count'); - obj.getAsValidNumber(filter_config, 'context_id_count'); - } - return configuration; - } catch (e) { - return new Error(error(e)); - } - } - - static inited(): boolean { - return true; - } - - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return { - filter_config: undefined, - fibex_file_paths: [], - with_storage_header: true, - tz: undefined, - }; - } - - protected getDefaultsFilters(): IFilters { - return { - min_log_level: LogLevel.Verbose, - app_ids: undefined, - ecu_ids: undefined, - context_ids: undefined, - app_id_count: 0, - context_id_count: 0, - }; - } - - public onOriginChange(origin: Origin.Configuration): void { - if (origin.instance instanceof Origin.Stream.Configuration) { - this.configuration.with_storage_header = false; - } else if (origin.instance instanceof Origin.File.Configuration) { - this.configuration.with_storage_header = - origin.instance.filetype() === Files.FileType.Binary; - } else if (origin.instance instanceof Origin.Concat.Configuration) { - // TODO: could be issue if concat configuration have different types - // of files - const types = origin.instance.filetypes(); - this.configuration.with_storage_header = - types.length === 0 ? true : types[0] === Files.FileType.Binary; - } else { - throw new Error(`Not implemented usecase for DLT parser onOriginChange`); - } - } - - public desc(): IList { - return Configuration.desc(); - } - - public setDefaultsFilterConfig(): void { - if (this.configuration.filter_config !== undefined) { - return; - } - this.configuration.filter_config = this.getDefaultsFilters(); - } - - public dropFilterConfigIfPossible(): void { - if (this.configuration.filter_config === undefined) { - return; - } - if (this.configuration.filter_config.min_log_level !== LogLevel.Verbose) { - return; - } - if ( - this.configuration.filter_config.app_ids !== undefined && - this.configuration.filter_config.app_ids.length > 0 - ) { - return; - } - if ( - this.configuration.filter_config.context_ids !== undefined && - this.configuration.filter_config.context_ids.length > 0 - ) { - return; - } - if ( - this.configuration.filter_config.ecu_ids !== undefined && - this.configuration.filter_config.ecu_ids.length > 0 - ) { - return; - } - this.configuration.filter_config = undefined; - } - - public override hash(): number { - const filters = - this.configuration.filter_config === undefined - ? this.getDefaultsFilters() - : this.configuration.filter_config; - return str.hash( - `dlt:${(this.configuration.fibex_file_paths === undefined - ? [] - : this.configuration.fibex_file_paths - ).join(';')};${this.configuration.with_storage_header};${this.configuration.tz};${ - filters.min_log_level - };${filters.ecu_ids?.length};${filters.app_ids?.length};${filters.context_ids?.length}`, - ); - } -} diff --git a/application/platform/types/observe/parser/index.ts b/application/platform/types/observe/parser/index.ts deleted file mode 100644 index 985e45be5..000000000 --- a/application/platform/types/observe/parser/index.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { - Configuration as Base, - ConfigurationStatic, - ReferenceDesc, - Linked, -} from '../configuration'; -import { Statics } from '../../../env/decorators'; -import { List, IList } from '../description'; -import { Mutable } from '../../unity/mutable'; -import { Alias } from '../../env/types'; - -import * as Dlt from './dlt'; -import * as SomeIp from './someip'; -import * as Text from './text'; -import * as Plugin from './plugin'; - -export * as Dlt from './dlt'; -export * as SomeIp from './someip'; -export * as Text from './text'; -export * as Plugin from './plugin'; -import * as Stream from '../origin/stream/index'; -import * as Files from '../types/file'; -import * as Origin from '../origin/index'; - -export type Reference = - | ReferenceDesc - | ReferenceDesc - | ReferenceDesc - | ReferenceDesc; - -export enum Protocol { - Dlt = 'Dlt', - SomeIp = 'SomeIp', - Text = 'Text', - Plugin = 'Plugin', -} - -export type IDeclaration = - | Text.IConfiguration - | Dlt.IConfiguration - | SomeIp.IConfiguration - | Plugin.IConfiguration; - -export type Declaration = - | Text.Configuration - | Dlt.Configuration - | SomeIp.Configuration - | Plugin.Configuration; - -export interface IConfiguration { - [Protocol.Dlt]?: Dlt.IConfiguration; - [Protocol.SomeIp]?: SomeIp.IConfiguration; - [Protocol.Text]?: Text.IConfiguration; - [Protocol.Plugin]?: Plugin.IConfiguration; -} - -const REGISTER: { - [key: string]: Reference; -} = { - [Protocol.Dlt]: Dlt.Configuration, - [Protocol.SomeIp]: SomeIp.Configuration, - [Protocol.Text]: Text.Configuration, - [Protocol.Plugin]: Plugin.Configuration, -}; - -export function tryAsEmbedded(alias: Protocol | string): Protocol | undefined { - if (REGISTER[alias]) { - return alias as Protocol; - } - return undefined; -} - -export function getAllRefs(): Reference[] { - return Object.keys(REGISTER).map((key) => REGISTER[key]); -} - -export abstract class Support { - public abstract getSupportedParsers(): Reference[]; -} - -const DEFAULT = Text.Configuration; - -export function getByAlias(alias: Protocol): Declaration { - const Ref: Reference = REGISTER[alias]; - if (Ref === undefined) { - throw new Error(`Unknown parser: ${alias}`); - } - return new Ref(Ref.initial(), undefined); -} - -export function suggestParserByFileExt(filename: string): Reference | undefined { - const normalized = filename.toLowerCase().trim(); - if (normalized.endsWith('.dlt')) { - return Dlt.Configuration; - } else if (normalized.endsWith('.pcapng')) { - return Dlt.Configuration; - } else { - return undefined; - } -} - -@Statics>() -export class Configuration - extends Base - implements List, Stream.Support, Files.Support, Origin.OnChange -{ - static alias(): Protocol { - throw new Error(`Alias of parsers holder should be used`); - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - if ( - Object.keys(REGISTER) - .map((k) => configuration[k as Protocol]) - .filter((v) => v !== undefined).length === 0 - ) { - return new Error(`Stream transport isn't defined`); - } - let error: Error | undefined; - Object.keys(REGISTER).forEach((key) => { - if (error instanceof Error) { - return; - } - const config: any = configuration[key as Protocol]; - if (config === undefined) { - return; - } - // Error with "never" comes because text parser has settings NULL - const err = REGISTER[key as Protocol].validate(config as never); - if (err instanceof Error) { - error = err; - } else { - error = undefined; - } - }); - return error instanceof Error ? error : configuration; - } - - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return { - [DEFAULT.alias()]: DEFAULT.initial(), - }; - } - - protected getProtocolKey(): Protocol | undefined { - let found: Protocol | undefined; - Object.keys(REGISTER).forEach((key) => { - if (found !== undefined) { - return; - } - if (this.configuration[key as Protocol] === undefined) { - return; - } - found = key as Protocol; - }); - return found; - } - - protected setInstance(): Configuration { - const protocol = this.getProtocolKey(); - if (protocol === undefined) { - throw new Error(`Configuration of stream doesn't have definition of known protocol.`); - } - if (this.instance !== undefined && this.instance.alias() === protocol) { - this.instance.setRef(this.configuration[protocol]); - return this; - } - this.instance !== undefined && this.instance.destroy(); - (this as Mutable).instance = new REGISTER[protocol]( - this.configuration[protocol], - { - watcher: this.watcher, - overwrite: (config: IConfiguration) => { - this.overwrite(config); - return this.configuration[protocol]; - }, - }, - ); - return this; - } - - public readonly instance!: Declaration; - - constructor(configuration: IConfiguration, linked: Linked | undefined) { - super(configuration, linked); - this.register( - this.watcher.subscribe(() => { - this.setInstance(); - }), - ); - this.setInstance(); - } - - public override destroy(): void { - super.destroy(); - this.instance !== undefined && this.instance.destroy(); - } - - public onOriginChange(origin: Origin.Configuration): void { - this.instance.onOriginChange(origin); - } - - public change(parser: Declaration): void { - this.overwrite({ [parser.alias()]: parser.configuration }); - } - - public desc(): IList { - return this.instance.desc(); - } - - public override getSupportedStream(): Stream.Reference[] { - return this.instance.getSupportedStream(); - } - - public override alias(): Protocol { - return this.instance.alias(); - } - - public override getSupportedFileType(): Files.FileType[] { - return this.instance.getSupportedFileType(); - } - - public as(Ref: { new (...args: any[]): Declaration } & Alias): T | undefined { - return this.instance.alias() === Ref.alias() ? (this.instance as T) : undefined; - } - - public override storable(): IConfiguration { - return { [this.instance.alias()]: this.instance.storable() }; - } - - public override hash(): number { - return this.instance.hash(); - } -} diff --git a/application/platform/types/observe/parser/plugin/index.ts b/application/platform/types/observe/parser/plugin/index.ts deleted file mode 100644 index ca3f52a18..000000000 --- a/application/platform/types/observe/parser/plugin/index.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { error } from '../../../../log/utils'; -import { Protocol } from '../index'; -import { Configuration as Base, ConfigurationStaticDesc } from '../../configuration'; -import { Statics } from '../../../../env/decorators'; -import { List, IList } from '../../description'; - -import * as str from '../../../../env/str'; -import * as Origin from '../../origin/index'; -import * as Stream from '../../origin/stream/index'; -import * as obj from '../../../../env/obj'; -import * as Files from '../../types/file'; - -import { PluginConfigItem, PluginConfigValue } from '../../../bindings/plugins'; - -export interface IConfiguration { - plugin_path: string; - plugin_configs: PluginConfigItem[]; -} - -@Statics>() -export class Configuration - extends Base - implements List, Stream.Support, Files.Support -{ - /// Plugin uses inner data to provide self description. In run-time fields - /// `major` will be replaced with plugin name; `minor` with an addition data - /// more details: `WrappedParserRef` (application/client/src/app/ui/tabs/observe/state.ts) - static desc(): IList { - return { - major: '', - minor: '', - icon: undefined, - }; - } - - static alias(): Protocol { - return Protocol.Plugin; - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - try { - const pluginPath = obj.getAsString(configuration, 'plugin_path'); - if (pluginPath.length == 0) { - return Error('No Valid Plugin Parser is selected.'); - } - - obj.getAsArray(configuration, 'plugin_configs'); - - return configuration; - } catch (e) { - return new Error(error(e)); - } - } - - static inited(): boolean { - return Configuration.desc().major.trim() !== ''; - } - - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return { - plugin_path: '', - plugin_configs: [], - }; - } - - public desc(): IList { - return { - major: 'Plugin parser instance major TODO', - minor: 'Plugin parser instance minor TODO', - icon: undefined, - }; - } - - public onOriginChange(_origin: Origin.Configuration): void { - // Plugin parsers don't need to react on changing source at the current implementation. - } - - /** - * Extract the value from the configuration value item depending on its kind, - * returning a value that can represented as a formatted string. - */ - getConfigValue(value: PluginConfigValue): string | number | boolean { - if ('Boolean' in value) { - return value.Boolean; - } - - if ('Integer' in value) { - return value.Integer; - } - - if ('Float' in value) { - return value.Float; - } - - if ('Text' in value) { - return value.Text; - } - - if ('Files' in value) { - return value.Files.join(','); - } - - if ('Directories' in value) { - return value.Directories.join(','); - } - - if ('Dropdown' in value) { - return value.Dropdown; - } - - assertNever(value); - } - - public override hash(): number { - const configs = this.configuration.plugin_configs.map( - (item) => `${item.id}:${this.getConfigValue(item.value)}`, - ); - return str.hash(`${this.configuration.plugin_path}; ${configs.join(';')}`); - } -} - -/** - * Helper function to ensure the remaining item is never for exhausting matching at compile time, - * and throwing an unhandled error at runtime. - */ -function assertNever(nev: never): never { - throw new Error(`Unhandled case: ${JSON.stringify(nev)}`); -} diff --git a/application/platform/types/observe/parser/someip/index.ts b/application/platform/types/observe/parser/someip/index.ts deleted file mode 100644 index eca02fd97..000000000 --- a/application/platform/types/observe/parser/someip/index.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { error } from '../../../../log/utils'; -import { Protocol } from '../index'; -import { Configuration as Base, ConfigurationStaticDesc } from '../../configuration'; -import { Statics } from '../../../../env/decorators'; -import { List, IList } from '../../description'; - -import * as Stream from '../../origin/stream/index'; -import * as obj from '../../../../env/obj'; -import * as Files from '../../types/file'; -import * as Origin from '../../origin/index'; -import * as str from '../../../../env/str'; - -export interface SomeipStatistic { - /** Statistic on service-ids and related method-ids */ - services: SomeipStatisticItem[]; - /** Statistic on message-types and related return-codes */ - messages: SomeipStatisticItem[]; -} - -export interface SomeipStatisticItem { - item: SomeipStatisticDetail; - details: SomeipStatisticDetail[]; -} - -export interface SomeipStatisticDetail { - id: number; - num: number; -} - -export interface IConfiguration { - fibex_file_paths: string[] | undefined; -} - -@Statics>() -export class Configuration - extends Base - implements List, Stream.Support, Files.Support -{ - static desc(): IList { - return { - major: 'SomeIp', - minor: 'Parsing SomeIp tracers', - icon: undefined, - }; - } - - static alias(): Protocol { - return Protocol.SomeIp; - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - try { - obj.getAsNotEmptyStringsArrayOrUndefined(configuration, 'fibex_file_paths'); - return configuration; - } catch (e) { - return new Error(error(e)); - } - } - - static inited(): boolean { - return true; - } - - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return { - fibex_file_paths: [], - }; - } - - public onOriginChange(_origin: Origin.Configuration): void { - //Do nothing - } - - public desc(): IList { - return Configuration.desc(); - } - - public override hash(): number { - return str.hash( - `someip:${(this.configuration.fibex_file_paths === undefined - ? [] - : this.configuration.fibex_file_paths - ).join(';')}`, - ); - } -} diff --git a/application/platform/types/observe/parser/text/index.ts b/application/platform/types/observe/parser/text/index.ts deleted file mode 100644 index 5a779c9fa..000000000 --- a/application/platform/types/observe/parser/text/index.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { error } from '../../../../log/utils'; -import { Protocol } from '../index'; -import { Configuration as Base, ConfigurationStaticDesc } from '../../configuration'; -import { Statics } from '../../../../env/decorators'; -import { List, IList } from '../../description'; - -import * as str from '../../../../env/str'; -import * as Origin from '../../origin/index'; -import * as Stream from '../../origin/stream/index'; -import * as Files from '../../types/file'; - -export type IConfiguration = null; - -@Statics>() -export class Configuration - extends Base - implements List, Stream.Support, Files.Support -{ - static desc(): IList { - return { - major: 'Plain Text', - minor: 'Plain Text Parser ', - icon: undefined, - }; - } - - static alias(): Protocol { - return Protocol.Text; - } - - static validate(configuration: IConfiguration): Error | IConfiguration { - try { - if (configuration !== null) { - throw new Error(`Text parser doesn't have any configuration; it should null`); - } - return configuration; - } catch (e) { - return new Error(error(e)); - } - } - - static inited(): boolean { - return true; - } - - // Gives initial settings. Not necessarily valid. - static initial(): IConfiguration { - return null; - } - - public onOriginChange(_origin: Origin.Configuration): void { - // Do nothing - } - - public desc(): IList { - return Configuration.desc(); - } - - public override hash(): number { - return str.hash(`text`); - } -} diff --git a/application/platform/types/observe/sde.ts b/application/platform/types/observe/sde.ts deleted file mode 100644 index a045fb719..000000000 --- a/application/platform/types/observe/sde.ts +++ /dev/null @@ -1,3 +0,0 @@ -export abstract class Support { - public abstract isSdeSupported(): boolean; -} diff --git a/application/platform/types/observe/types/file/index.ts b/application/platform/types/observe/types/file/index.ts deleted file mode 100644 index b3ec14e01..000000000 --- a/application/platform/types/observe/types/file/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -export abstract class Support { - public abstract getSupportedFileType(): FileName[]; -} - -export enum FileType { - PcapNG = 'PcapNG', - PcapLegacy = 'PcapLegacy', - Text = 'Text', - Binary = 'Binary', - ParserPlugin = 'ParserPlugin', -} - -export function getFileTypeFrom(smth: unknown): FileType | Error { - switch (smth as FileType) { - case FileType.Binary: - return FileType.Binary; - case FileType.PcapNG: - return FileType.PcapNG; - case FileType.PcapLegacy: - return FileType.PcapLegacy; - case FileType.Text: - return FileType.Text; - case FileType.ParserPlugin: - return FileType.ParserPlugin; - default: - return new Error(`${smth} isn't FileType`); - } -} - -export function extname(filename: string): string { - const matches = filename.match(/\.[\w\d_]*$/gi); - if (matches !== null) { - return matches[0]; - } - return ''; -} - -export type FileName = string; diff --git a/application/platform/types/observe/types/index.ts b/application/platform/types/observe/types/index.ts deleted file mode 100644 index 139f68171..000000000 --- a/application/platform/types/observe/types/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * as File from './file'; -export * as Plugin from './plugin'; -export { ISourceLink } from './sourcelink'; diff --git a/application/platform/types/observe/types/plugin/index.ts b/application/platform/types/observe/types/plugin/index.ts deleted file mode 100644 index eee207940..000000000 --- a/application/platform/types/observe/types/plugin/index.ts +++ /dev/null @@ -1 +0,0 @@ -export type PluginDirPath = string; diff --git a/application/platform/types/observe/types/sourcelink.ts b/application/platform/types/observe/types/sourcelink.ts deleted file mode 100644 index a138ce754..000000000 --- a/application/platform/types/observe/types/sourcelink.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ISourceLink { - id: number; - alias: string; -} diff --git a/cli/chipmunk-cli/Cargo.lock b/cli/chipmunk-cli/Cargo.lock index ee3b5dda7..ee4fb6aef 100644 --- a/cli/chipmunk-cli/Cargo.lock +++ b/cli/chipmunk-cli/Cargo.lock @@ -329,6 +329,18 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "components" +version = "0.1.0" +dependencies = [ + "log", + "stypes", + "thiserror 2.0.12", + "tokio", + "tokio-util", + "uuid", +] + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -386,6 +398,16 @@ dependencies = [ "syn", ] +[[package]] +name = "definitions" +version = "0.1.0" +dependencies = [ + "etherparse", + "serde", + "stypes", + "thiserror 2.0.12", +] + [[package]] name = "derive_builder" version = "0.20.2" @@ -477,9 +499,9 @@ dependencies = [ [[package]] name = "etherparse" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d8a704b617484e9d867a0423cd45f7577f008c4068e2e33378f8d3860a6d73" +checksum = "3ff83a5facf1a7cbfef93cfb48d6d4fb6a1f42d8ac2341a96b3255acb4d4f860" dependencies = [ "arrayvec", ] @@ -493,6 +515,13 @@ dependencies = [ "syn", ] +[[package]] +name = "file-tools" +version = "0.1.0" +dependencies = [ + "anyhow", +] + [[package]] name = "fnv" version = "1.0.7" @@ -754,6 +783,26 @@ dependencies = [ "libc", ] +[[package]] +name = "libudev" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0" +dependencies = [ + "libc", + "libudev-sys", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "lock_api" version = "0.4.13" @@ -930,10 +979,12 @@ dependencies = [ name = "parsers" version = "0.1.0" dependencies = [ - "byteorder", "chrono", "chrono-tz", + "components", + "definitions", "dlt-core", + "file-tools", "lazy_static", "log", "memchr", @@ -943,7 +994,9 @@ dependencies = [ "someip-messages", "someip-payload", "someip-tools", - "thiserror 2.0.12", + "stypes", + "tokio-util", + "uuid", ] [[package]] @@ -1007,6 +1060,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1242,6 +1301,7 @@ dependencies = [ "core-foundation", "core-foundation-sys", "io-kit-sys", + "libudev", "mach2", "nix 0.26.4", "scopeguard", @@ -1343,7 +1403,11 @@ dependencies = [ "async-stream", "bufread", "bytes", + "components", + "definitions", + "envvars", "etherparse", + "file-tools", "futures", "indexer_base", "lazy_static", @@ -1352,6 +1416,7 @@ dependencies = [ "pcap-parser", "regex", "serde", + "serialport", "shellexpand", "socket2", "stypes", @@ -1388,6 +1453,7 @@ dependencies = [ "serde", "thiserror 2.0.12", "tokio", + "tokio-util", "uuid", "walkdir", ] diff --git a/cli/development-cli/src/release/mod.rs b/cli/development-cli/src/release/mod.rs index 2b4d8cd3a..fc5fd8047 100644 --- a/cli/development-cli/src/release/mod.rs +++ b/cli/development-cli/src/release/mod.rs @@ -87,7 +87,8 @@ pub async fn do_release(development: bool, code_sign_path: Option) -> a ); let results = jobs_runner::run( - &[Target::App, Target::Updater, Target::CliChipmunk], + // &[Target::App, Target::Updater, Target::CliChipmunk], + &[Target::App, Target::Updater], JobType::Build { production: !development, }, @@ -159,26 +160,26 @@ pub async fn do_release(development: bool, code_sign_path: Option) -> a // *** Compressing *** - println!( - "{}", - style("Start Compressing release files...") - .blue() - .bright() - .bold() - ); - - let compress_start = Instant::now(); - - compress().await?; - compress_cli() - .await - .context("Error while compressing Chipmunk CLI binary")?; - - let finish_msg = format!( - "Compressing succeeded in {} seconds.", - compress_start.elapsed().as_secs().max(1) - ); - println!("{}", style(finish_msg).green().bold()); + // println!( + // "{}", + // style("Start Compressing release files...") + // .blue() + // .bright() + // .bold() + // ); + + // let compress_start = Instant::now(); + + // compress().await?; + // compress_cli() + // .await + // .context("Error while compressing Chipmunk CLI binary")?; + + // let finish_msg = format!( + // "Compressing succeeded in {} seconds.", + // compress_start.elapsed().as_secs().max(1) + // ); + // println!("{}", style(finish_msg).green().bold()); print_log_separator(); diff --git a/docs/charts.md b/docs/charts.md index ab3834a95..da8431573 100644 --- a/docs/charts.md +++ b/docs/charts.md @@ -1,6 +1,6 @@ In addition to search, `chipmunk` provides tools for analyzing metrics and any kind of numerical data. Users can define regular expressions to extract specific values, which are then used to generate charts. -A key advantage of `chipmunk` is its near insensitivity to data volume — it performs equally well when processing a few hundred values or several million. +A key advantage of `chipmunk` is its near insensitivity to data volume - it performs equally well when processing a few hundred values or several million. ### Chart creating diff --git a/docs/index.md b/docs/index.md index 04a8085c6..ad5f4a691 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,7 +2,7 @@ [![](https://github.com/esrlabs/chipmunk/actions/workflows/release_next.yml/badge.svg)](https://github.com/esrlabs/chipmunk/actions/workflows/release_next.yml) [![](https://github.com/esrlabs/chipmunk/actions/workflows/lint_master.yml/badge.svg)](https://github.com/esrlabs/chipmunk/actions/workflows/lint_master.yml) -`chipmunk` is one of the fastest desktop applications for viewing log files, with no limitations on file size. 1 GB, 2 GB, 10 GB? `chipmunk` is limited only by your disk space — nothing more. With no caching and no unnecessary copying, files of any size open with the same speed. But `chipmunk` goes beyond just working with files: it also allows you to create network connections to collect logs via TCP, UDP, Serial, or from the output of a running command. +`chipmunk` is one of the fastest desktop applications for viewing log files, with no limitations on file size. 1 GB, 2 GB, 10 GB? `chipmunk` is limited only by your disk space - nothing more. With no caching and no unnecessary copying, files of any size open with the same speed. But `chipmunk` goes beyond just working with files: it also allows you to create network connections to collect logs via TCP, UDP, Serial, or from the output of a running command. ## Automotive and Network Traces @@ -19,7 +19,7 @@ Additionally, `chipmunk` allows you to work with DLT traces both as standalone f - Serial Port - Output from a command or program -For each source, you can assign a parser — for example, to collect DLT packets over a UDP connection for analysis or to save them as a standalone trace file. +For each source, you can assign a parser - for example, to collect DLT packets over a UDP connection for analysis or to save them as a standalone trace file. Another key feature is the ability to launch any command or program and collect its output, which can be analyzed in real time as it's generated. @@ -29,7 +29,7 @@ At its core, `chipmunk` is a log analysis tool. It goes beyond simple search que ![filters_create](assets/search/filters_create.gif) -The search engine works dynamically — results are updated in real time as new data is added. If you're connected to a live data source, your active filters will continuously update the search results as new logs arrive. +The search engine works dynamically - results are updated in real time as new data is added. If you're connected to a live data source, your active filters will continuously update the search results as new logs arrive. ## Metrics, Measurements, and Graphs diff --git a/docs/installation.md b/docs/installation.md index 2ee301b49..51088b563 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,5 +1,5 @@ -`chipmunk` is distributed as a portable version and does not require any installation — just download and run. Additionally: +`chipmunk` is distributed as a portable version and does not require any installation - just download and run. Additionally: - It does not install or depend on any external libraries or runtime environments (with a small exception for Windows). - It includes a built-in update mechanism (you will be notified when a new version is available). diff --git a/docs/teamwork.md b/docs/teamwork.md index 69606972d..24d9d7c31 100644 --- a/docs/teamwork.md +++ b/docs/teamwork.md @@ -1,4 +1,4 @@ -`chipmunk` supports collaborative work on log file analysis. However, it remains a fully standalone application that requires no installation or server infrastructure. Staying true to this philosophy, we use GitHub repositories to enable shared access to data — providing both secure data exchange and access control. +`chipmunk` supports collaborative work on log file analysis. However, it remains a fully standalone application that requires no installation or server infrastructure. Staying true to this philosophy, we use GitHub repositories to enable shared access to data - providing both secure data exchange and access control. ## Before start