Compare commits

...
Author SHA1 Message Date
John Colvin 84351460b5 refactor: Add IDataStreamFactory(s) for spatial arb streams 2025-08-24 13:46:58 -04:00
John Colvin 9a1fee4e83 Support custom stream argument parsers 2025-08-24 13:46:58 -04:00
John Colvin dd55866056 Make stream arg name regex match case insensitive 2025-08-24 13:46:57 -04:00
John Colvin 8f6e2b8512 tool: Run Spark.exe from its own directory
It looks in the working directory for runtime config appsettings.json files.
2025-08-24 13:46:57 -04:00
John Colvin b9a36f5d51 cleanup: Remove unused BooleanExpressionToken Negate 2025-08-24 13:46:57 -04:00
John Colvin 3a3bec5821 doc: Comment why MDConsumer must be kept alive
The language server suggests removing it because it isn't referenced.
However, it contains unmanaged resources that mustn't be prematurely
garbage collected.
2025-08-24 13:46:57 -04:00
John Colvin 05a5e9dcf8 refactor: Rename AddRemoteQueryManager -> AddRemoteQueryStreams 2025-08-24 13:46:57 -04:00
John Colvin b1211ed590 refactor: Hide md client behind interface, create via Dep Injection 2025-08-24 13:46:57 -04:00
John Colvin d9a2ab00f8 Drop dump-books and dump-securities from spark_api_server cli
These were subcommands were out of place. This exe has one purpose: run
the Spark Api Server.
2025-08-24 13:46:57 -04:00
John Colvin 22ea8a7070 cleanup: Remove unused MarketDataClient from OrderSample 2025-08-24 13:46:57 -04:00
John Colvin 1b7543e94b build: Launch console for Spark Debug builds, otherwise hide it 2025-08-24 13:46:57 -04:00
John Colvin c66d8c1f99 cleanup: Remove ancient winforms sdk ui example 2025-08-24 13:46:57 -04:00
John Colvin 87d6a97198 cleanup: Fix a ton of build warnings (cs)
All these warnings have been bothering me for a while. The work isn't done, but
this commit chops them down from ~1000 to ~600.

* AppendCollection.cs
* AppendStreamModified.cs
* BenzingaNewsStream.cs
* BenzingaNewsSymbolStateStream.cs
* BookStream.cs
* CSVSaverStream.cs
* ChartsUtils.cs
* ColumnInfo.cs
* ContractUtils.cs
* ContractUtils.cs
* DiffStream.cs
* FuturesActiveContractStream.cs
* FuzzyJoinOverTimeStream.cs
* HistoricalBarsStream.cs
* HistoricalBarsStream.cs
* InstrumentsStream.cs
* JoinOperandRowIndex.cs
* JoinRow.cs
* JoinStream.cs
* JoinTable.cs
* JoinTableSchema.cs
* LatencyStream.cs
* LeftInternalJoinStream.cs
* LightForeignJoinRow.cs
* LightForeignJoinRowList.cs
* LightLeftForeignJoinRow.cs
* LiteralStream.cs
* NLStream.cs
* NLToDSLStream.cs
* NLToIndexedFewShotPromptStream.cs
* NonIncGroupByStream.cs
* NonIncJoinBookkeeper.cs
* NonIncJoinSpec.cs
* OHLCVStream.cs
* OpenAIPromptStream.cs
* PriceStatsStream.cs
* PriceStatsStream.cs
* ProjectHelper.cs
* ProjectStream.cs
* PubSubServerStateStream.cs
* PubsubHeartbeatStream.cs
* SimpleNode.cs
* SortRow.cs
* SpatialArbStatsStream.cs
* SpatialFuturePairsStream.cs
* SpreadPerformanceEvaluator.cs
* SpreadPerformanceStream.cs
* StreamIOCommon.cs
* SuperJoinStream.cs
* TableMetaDataStream.cs
* TextWriterStream.cs
* TimeAverageStream.cs
2025-08-24 13:46:57 -04:00
John Colvin cf05d07692 style: Normalize indentation in PubsubHeartbeatStream.cs 2025-08-24 13:46:56 -04:00
John Colvin 9ccc4467ba chore: Add GetValue, TryGetValue ext fns to IBlueRow 2025-08-24 13:46:56 -04:00
John Colvin 9c2223b4da chore: Add 'Empty' fn for BlueTable/Schema (for convenience) 2025-08-24 13:46:56 -04:00
John Colvin b09a39ceb6 cleanup: Remove redundant code block in HistoricalBarsStream.cs 2025-08-24 13:46:56 -04:00
John Colvin 8ebf437c9e cleanup: Fix indentation and build warnings in TestStream.cs 2025-08-24 13:46:56 -04:00
John Colvin 160dbb624f fix (preemptive): Avoid StackOverflow in md client securities fn
One of the 'securities_incremental' fns was calling back into itself instead of
the other overload. This is a 'preemptive' fix because the problematic code path
wasn't running in practice (though it was a ticking time bomb).
2025-08-24 13:46:56 -04:00
John Colvin da945b39c7 cleanup: Make MDConsumer.on_security nonnull
It's always initialized in the constructor.
2025-08-24 13:46:56 -04:00
John Colvin bfede66c31 refactor: Decouple ViewStreamDatabase from TableStream, ISubscriberCore 2025-08-24 13:46:56 -04:00
John Colvin bc39b8fda9 chore: Add position index to IDataStreamFactory input args 2025-08-24 13:46:56 -04:00
John Colvin 12fadab839 check pt: PingPong algo (2) 2025-08-24 13:46:56 -04:00
John Colvin bcbfc56e7d check pt: PingPong algo 2025-08-24 13:46:56 -04:00
John Colvin efd389ea46 Add environment variable override for app data path
Historically, ~/sparkdata on linux, and ~/AppData/Roaming/bts on win.
Now that we may run multiple exes at once (api server, pubsub, sparkd, etc.),
we need to support multiple data dirs on the same host.
2025-08-24 13:46:56 -04:00
John Colvin ce236da67b Drop ".exe" from log dir name on windows 2025-08-24 13:46:56 -04:00
John Colvin e91fb32207 feat: Add SpreadBook stream 2025-08-24 13:46:56 -04:00
John Colvin 0cf576a055 fix: Check settings Subject.IsDisposed before using 2025-08-24 13:46:56 -04:00
John Colvin 7213b05c9e ui (launcher): Move "X" button to left of report menu items
This is a more natural location. Also, it looked weird on the right because the
menu labels aren't fixed width, so the buttons weren't vertically aligned.
2025-08-24 13:46:55 -04:00
John Colvin 19a6f53cfa refactor: Use HostBuilder to launch spark w/ streams 2025-08-24 13:46:55 -04:00
John Colvin 7a53bf4b01 build (cleanup): Get rid of global.json
We don't need it anymore
2025-08-24 13:46:55 -04:00
John Colvin 965186874e build: Update System.Composition pkg refs to 8.0.0 2025-08-24 13:46:55 -04:00
John Colvin f8704dd083 cleanup: Mark a few classes "internal" in Spark 2025-08-24 13:46:55 -04:00
John Colvin 93c2770abe cleanup: Use consistent access modifier in TabDocument partial class
It should be marked internal in both xaml and xaml.cs (it was previously
only so in the latter).
2025-08-24 13:46:55 -04:00
John Colvin f422b752a4 refactor: Consolidate ShellBootstrapper and base class into one 2025-08-24 13:46:55 -04:00
John Colvin d6f7031716 feat: Add Stream Reports shell window 2025-08-24 13:46:55 -04:00
John Colvin 5d34bdbecf build: Define custom "main" (entry point) for Spark exe
This is a prelude to integrating streams into spark. The plan is to use
an ms HostBuilder to instantiate stream services/dependencies alongside
the spark Application object.
2025-08-24 13:46:55 -04:00
John Colvin 0c96c3bc06 cleanup: Get rid of stunnel code
This looks like it hasn't been hooked up for years...it would only start
if a stunnel.conf file was detected, which hasn't been around for a very
long time.
2025-08-24 13:46:55 -04:00
John Colvin 1da06732e2 fix: Only create mutex if spark app is NOT running in vs designer
I accidentally droped the '!' in a cleanup commit 4 years ago
2025-08-24 13:46:55 -04:00
John Colvin 641f00b4e0 fix Extend dsl to support explicit param names 2025-08-24 13:46:55 -04:00
John Colvin d860573910 vscode: Add DSQA debug cfg to skip build 2025-08-24 13:46:55 -04:00
John Colvin fa8f623263 fix (dsqa): Resolve name conflicts with new BlueOrb streams 2025-08-24 13:46:55 -04:00
John Colvin 706d9235ca fix: Fix typo in "DateTimeExpansionStream" type name
Previously, it was "DateTimeExpanstion" (erroneous "t")
2025-08-24 13:46:55 -04:00
John Colvin 03c99cc3df chore: Add CircularBuffer class (c#) 2025-08-24 13:46:55 -04:00
John Colvin fc050f4bd3 refactor: Rename ExceptionAssert.is_valid ExceptionAssert.Index.IsValid
Like the ExceptionAssert.Argument class that provides methods that throw
ArgumentException if a condition isn't satisfied, the ExceptionAssert.Index
class provides methods that throw IndexOutOfRange exceptions.
2025-08-24 13:46:55 -04:00
John Colvin 39b3490f5a chore: Add OffsetList class
This supports mutations (unlike the SubRangeWrapper class).
2025-08-24 13:46:54 -04:00
John Colvin a0ef2c3348 cleanup: Remove "partial" class modifiers from OrderBook vm classes
They simply weren't necessary.
2025-08-24 13:46:54 -04:00
John Colvin acb420d4cc check pt: Proof of concept ladder left panel price point collection 2025-08-24 13:46:54 -04:00
John Colvin ab07697985 ui: Adjust ladder ticks-per-row with ctrl+shift+mousewheel 2025-08-24 13:46:54 -04:00
John Colvin 6ad7418931 build: Change publish_data_stream.py to publish the sln
We were publishing individual projects as a workaround for the
dependency issue (bug) that caused some projects to incorrectly target
net8.0. The fact that publishing individual projects "just worked" was
probably dumb luck. Now that the underlying issue has been addressed, we
can resume publishing the sln as a whole.
2025-08-24 13:46:54 -04:00
John Colvin 783764fc1f build: Add scripts to publish data stream apps 2025-08-24 13:46:54 -04:00
John Colvin 5771b19c18 build: Use separate cmd to build DataStreamSuite/QueryAnalyzer.sln
The DataStreamSuite.sln builds the new webapp now, which will
(eventually) replace DSQA. We'll keep it around for now, but with a
clean separation from the main build.
2025-08-24 13:46:54 -04:00
John Colvin ee5b78ed9c feat: Add DataStream WebApp
Included in DataStreamSuite.sln, but not DataStreamQueryAnalyzer.sln
2025-08-24 13:46:54 -04:00
John Colvin d609dcd3c2 cleanup: Remove unused code 2025-08-24 13:46:54 -04:00
John Colvin 7b266cd4d0 Reduce log noise in AsyncTCPConnection_AsyncWrapper 2025-08-24 13:46:54 -04:00
John Colvin de8593af5a TEMP changes to appsettings.json (at least i should review them before making permanent) 2025-08-24 13:46:54 -04:00
John Colvin ab6f6e1790 Add ColumnIndex class to resolve column names to indices
Improves performance compared to accessing columns by name
2025-08-24 13:46:54 -04:00
John Colvin 51e38ca793 Increase SQLiteEnumerator page size to 65536
This performs better, and doesn't seem to be too large in practice
2025-08-24 13:46:53 -04:00
John Colvin 7b2bc4b10c Serialize TimeSpan w/ microsecond precision in DataPacket json 2025-08-24 13:46:53 -04:00
John Colvin 05cae5c959 Format DateTimes w/ microsecond precision in DataPacketJsonConverter 2025-08-24 13:46:53 -04:00
John Colvin aaca8f0f3a Add BlueColumnTable for pandas integration
This will make it easier/more natural to support pandas column expressions
2025-08-24 13:46:53 -04:00
John Colvin 59a16677d1 Add c# Variant class, modeled after std::variant in c++ 2025-08-24 13:46:53 -04:00
John Colvin 50e7bba3d7 refactor: Rename snake_case fns PascalCase 2025-08-24 13:46:53 -04:00
John Colvin 6026ca0c62 refactor: Use dependency injection in DSQA 2025-08-24 13:46:53 -04:00
John Colvin 253512cef8 refactor: Load IDataStreamFactory(s) with dependency injection
So we can create ViewStreams that depend on other services, config, etc.
Previously, the PrimitiveManager relied purely on static construction of
types through reflection.
2025-08-24 13:46:53 -04:00
John Colvin ae050d9401 chore: Make BlueTableSchemaAdapter implement IBlueTableSchema 2025-08-24 13:46:53 -04:00
John Colvin eb0f386328 refactor: Rename "BlueTableSchemaAdaptor" with "er" 2025-08-24 13:46:53 -04:00
John Colvin b5aca5637c refactor: Replace DataPacket w/ IDataPacket in stream handlers 2025-08-24 13:46:53 -04:00
John Colvin 685507eed4 refactor: Change namespace to Bts.DataStreamCore for a few types
Since they're in the DataStreamCore csproj
2025-08-24 13:46:53 -04:00
John Colvin 6567baa7d5 Make 'name' default to CallerArgumentExpression for convenience 2025-08-24 13:46:52 -04:00
John Colvin 39dc6c24ed refactor: Replace if/else with switch cases 2025-08-24 13:46:52 -04:00
John Colvin 317a8cee60 cleanup: Remove over-engineered subcommands
We don't need explicit subcommands to 'run onixs' md (just use config).
2025-08-24 13:46:52 -04:00
John Colvin 9bb7beabc2 fix: Ensure IDGenerator instance is created before deref 2025-08-24 13:46:52 -04:00
John Colvin 2364c82254 fix: Expand undefined vars to empty string
Bugs (fixed):
  * Md shared memory file names have unexpanded variable: '${BTS_SHARED_MEMORY_PREFIX}'
2025-08-24 13:46:52 -04:00
John Colvin b3114f2a62 fix: Emplace MDConsumerHandle instead of copy assign 2025-08-24 13:46:52 -04:00
John Colvin b64a8ba214 cleanup: Remove unnecessary code
Trim includes as well
2025-08-24 13:46:52 -04:00
John Colvin b257b673fb improvement: Set SHARED_MEMORY_PREFIX to an environment variable
So we can effectively define it at runtime. We don't want to have to
produce different builds just to change the shared mem prefix.
2025-08-24 13:46:52 -04:00
John Colvin 43df2bbdc5 fix: Always prepend proc name to shared mem prefix
Our shared memory model isn't really designed to be shared across
processes. Maybe some day it will.
2025-08-24 13:46:52 -04:00
John Colvin 5f80492c93 cleanup: Remove redundant macro def 2025-08-24 13:46:52 -04:00
John Colvin 5a63b25a8f Skip md recording thread loop if no recorders configured to run 2025-08-24 13:46:51 -04:00
John Colvin 5647beed40 cleanup: Trivial whitespace cleanup in Protobuf.cmake 2025-08-24 13:46:51 -04:00
John Colvin d3900e3149 build: Clean up data_files cmake proj (support --target clean) 2025-08-24 13:46:51 -04:00
John Colvin 221505e243 fix build: Add ShmSegmentName.cpp to windows client sources 2025-08-24 13:46:51 -04:00
John Colvin d16c696ecf Add api server to docker compose 2025-08-24 13:46:51 -04:00
John Colvin c371fd8e99 Change api server ports so they don't conflict w/ ds webapp 2025-08-24 13:46:51 -04:00
John Colvin 16fb295a37 build: Resolve app data dir and shared mem prefix at runtime
We really can't define them at compile time anymore as we build multiple
exes that depend on libspark in the same dir (e.g., spark api server,
pubsub, ds-publisher, webapp, etc.). That means one libspark.so (or dll)
must serve all exes. We should compile a consistent libspark to mitigate
the risk of running an exe with the wrong version (of libspark). For
example, running pubsub with a libspark configured for spark api server,
leftover from an earlier build.
2025-08-24 13:46:51 -04:00
John Colvin bb157c768c Don't log "initializing spark config" if it has already been initialized 2025-08-24 13:46:51 -04:00
John Colvin 8c9ac51bb6 vscode: Add debug cfg for spark api server 2025-08-24 13:46:51 -04:00
John Colvin 39d04285d0 Use dependency injection in spark api server 2025-08-24 13:46:51 -04:00
John Colvin 41db558841 Add SparkLoggerAdapter for Msft ILogger 2025-08-24 13:46:51 -04:00
John Colvin cdebdd5765 fix: Don't assert on exception in BooleanExpression.evaluate
Bugs (fixed):
  * DataStream WebApp Server sometimes crashes when the user enters a
    bad query.
2025-08-24 13:46:50 -04:00
John Colvin bb7a000689 Assert that local ip address is successfully obtained 2025-08-24 13:46:50 -04:00
John Colvin d62816ac0b improvement: Resolve hostnames when comparing ip addrs
So we can to configure pubsub routes by hostname or ipv4 (ipv6 hasn't
been tested yet...so we should assume it doesn't work).
2025-08-24 13:46:50 -04:00
John Colvin 964ec5b565 Assert TcpConnection endpoint matches expected 2025-08-24 13:46:50 -04:00
John Colvin 46247b6439 Catch exceptions on AsyncTCPConnector_AsyncWrapper background thread
Since it spawns a new thread on each attempt, uncaught exceptions were previously undetected,
and caused silent failures down the pipe. This should make it easier to track such bugs down.
2025-08-24 13:46:50 -04:00
John Colvin 10e862074c fix: Format PubSubRoute as json in log msgs
It was previously using the default ToString, which simply returns the type name
2025-08-24 13:46:50 -04:00
John Colvin 9ce0bd1e35 build: Upgrade packages (deps) in DataStreamTests 2025-08-24 13:46:50 -04:00
John Colvin 6d93cf856f cleanup: Remove unused ViewStreamDatabase config 2025-08-24 13:46:50 -04:00
John Colvin fd68164eef vscode: Add PubSubServer debug cfg to skip build 2025-08-24 13:46:50 -04:00
John Colvin 863efdd53f Remove reference to spark protobuf Config in ViewStream
We can't read spark protobuf Config until it has been initialized,
and since ViewStream doesn't have the configServer param handy it
isn't able to initialize config if necessary. In that case, it would simply
block until config was initialized by someone else. Such a risk doesn't
seem justified for this particular configuration dependency
(DataStreamConfig.UseUncopiedNewRowsInFire). For now, its hardcoded to false.
2025-08-24 13:46:50 -04:00
John Colvin 3d94eabab7 refactor: Run RemoteQueryManager as an IHostedService
This paradigm more accurately represents the RemoteQueryManager as a
Service that can be started/stopped.
2025-08-24 13:46:50 -04:00
John Colvin 450ef9985a fix: Ensure spark config init and login are only performed once
As a static function that could be called from anywhere in the code
(directly or indirectly), 'Configuration.Initialize' was vulnerable to
race bugs. The "Configuration.get<T>()" method, whose calls are
ubiquitous in our code, would initialize config if it hadn't been done
so already. Without a reference to the configServer param, this param
would effectively be ignored/overridden with null.

It's important that login only be performed once, for obvious reasons
(our exes that connect to sparkd are generally designed to process data
for a single user). Furthermore, not every routine that requires a
successful login has a reference to the credentials needed to do it. To
address this, we now pass the ISparkdLoginService singleton to these
routines so they can wait until the singular login task has completed
before they begin.
2025-08-24 13:46:50 -04:00
John Colvin 0bf7e209a0 Assert ViewStreamDatabase.instance is only created once
The instance must be initialized via ViewStreamDatabase.SetGlobalInstance
2025-08-24 13:46:50 -04:00
John Colvin b5c666c505 cleanup: Remove unused code from ViewStreamDatabase 2025-08-24 13:46:49 -04:00
John Colvin 198cda76a8 fix: Define BoooleanConstant before StringConstant
Otherwise the former masks the latter and prevents it from matching
2025-08-24 13:46:49 -04:00
John Colvin dc17774e07 cleanup: Remove unnecessary deps from csprojs 2025-08-24 13:46:49 -04:00
John Colvin c9c33045f4 fix: Catch exception when creating TcpClient 2025-08-24 13:46:49 -04:00
John Colvin e36b3bf115 fix: Make SystemSubscriptionMapPublisher threadsafe
The PubSubState.onTagMapChanged event may not be invoked on
the same thread as doWork.
2025-08-24 13:46:49 -04:00
John Colvin a6383c08a4 fix: Add a few null checks
* TimeBasedFilter
* TableAccumulator2
* NonIncGroupByStream
2025-08-24 13:46:49 -04:00
John Colvin ee651061b9 Reduce task wait/timeout code in AsyncTCPConnector 2025-08-24 13:46:49 -04:00
John Colvin 2a497b460d fix build warnings: Make props nullable strings in SharedStreamConfig 2025-08-24 13:46:49 -04:00
John Colvin ac5edfb0ce Assert that System publishers 'doWork' is always called on same thread 2025-08-24 13:46:49 -04:00
John Colvin 54cf46e926 deploy: Add pubsub and md-publisher services to compose.yaml 2025-08-24 13:46:49 -04:00
John Colvin 116778a0cc refactor: Model config profiles after dotnet appsettings
Namely, in DataStreamPublisher and PubSubServer. This is part of a
broader paradigm shift towards dotnet factory pattern.

https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines

* Rename Configurations/<name>.json -> appsettings.<name>.json
* Move reusable startup code into new Bts.Extensions.Hosting proj
2025-08-24 13:46:49 -04:00
John Colvin f7b835997e Add ErrorChanged event to propagate DataStream exceptions 2025-08-24 13:46:49 -04:00
John Colvin c920c6f3a9 fix build: Fix Spark build after making config init async 2025-08-24 13:46:49 -04:00
John Colvin 36de4d1578 refactor: Tweak SparkCoreServices to synchronize Configuration.init globally 2025-08-24 13:46:49 -04:00
John Colvin 0571cb40a0 refactor: Change Configuration.initialize to be async 2025-08-24 13:46:48 -04:00
John Colvin 1ca6acdd23 vscode: Add 'isSparkApiEnabled' input to launch.json 2025-08-24 13:46:48 -04:00
John Colvin f4f3c881db Remove unused PubSubConfig props 2025-08-24 13:46:48 -04:00
John Colvin 36e150f6c7 Capture OperationCanceledException (instead of TaskCanceledException)
The former is the base class for the latter, so this will widen the umbrella to catch a few corner cases
that would otherwise been handled by the generic "Exception" catch block. One corner case I know
of is when an op is canceled via "CancellationToken.ThrowIfCancellationRequested". In that case,
an OperationCanceledException is thrown (not a TaskCanceledException).

https://stackoverflow.com/questions/34359129/operationcanceledexception-vs-taskcanceledexception-when-task-is-canceled
2025-08-24 13:46:48 -04:00
John Colvin f98b435e0b Fix dsqa config json 2025-08-24 13:46:48 -04:00
John Colvin dce4c5f543 fix: Always call 'handle_internal_pubs' in PubSubMessageReactor main loop 2025-08-24 13:46:48 -04:00
John Colvin d4d696e135 Fix mismatched types in PubSubServerTagStatsStream schema 2025-08-24 13:46:48 -04:00
John Colvin bb818d9dcc Add DataStreamPublisher service modules designed for Dependency Injection
* Change DataStreamPublisher json fields to match cs class property names verbatim
* Rename Publishers -> PublisherNodes in DataStreamPublisherConfig
2025-08-24 13:46:48 -04:00
John Colvin 14e838de6a refactor: Decouple ViewPublisher from IPubTableBroker impl 2025-08-24 13:46:48 -04:00
John Colvin 6fe35e2692 Add PubSub service modules designed for Dependency Injection
* Get rid of JsonPropertyName attributes in PubSubConfig
  - The Dependency Injection framework (based around dotnet ApplicationBuilder)
    does not support this attribute, and maps property names verbatim to json fields.

* Change PubSubServer json fields to match cs class property names verbatim
2025-08-24 13:46:48 -04:00
John Colvin 08f9784ed0 refactor: Decouple RemoteQueryManager from IManagedSubscriber, IPubTableBroker impl 2025-08-24 13:46:48 -04:00
John Colvin bde4ee6308 refactor: Decouple ManagedSubscriber from ISubscriberCore impl 2025-08-24 13:46:48 -04:00
John Colvin 03116a2973 refactor: Only support 1 tag type in IPubSubServerTagHandler 2025-08-24 13:46:48 -04:00
John Colvin d0d6d587d7 refactor: Decouple PubTableBroker from IPublisherCore impl
Reference interface rather than the PublisherCore impl.
2025-08-24 13:46:48 -04:00
John Colvin 765e45d95a fix: Dispose market data sub in various md streams 2025-08-24 13:46:48 -04:00
John Colvin cd833ceb36 cleanup: Remove unused pubsub tag handler code
* Removed 'IPubSubServerTagHandler.register'
  - All implementations of this function were empty
* Removed 'IPubSubServerTagHandler.supportedTagTypes'
  - This property wasn't referenced anywhere
* Remove IWorker inheritance from IPubSubServerTagHandler
  - No reason these interfaces have to be tied together
2025-08-24 13:46:47 -04:00
John Colvin a148a03175 refactor: Rename CloudStreamManager.cs -> RemoteQueryManager.cs
This matches the file name
2025-08-24 13:46:47 -04:00
John Colvin 338edfd732 Change pub sub schema prefix 'bluto://' -> 'bts-cloud://'
In config json files (not actual code change)
2025-08-24 13:46:47 -04:00
John Colvin 2a9ca60654 chore: Fix typos in log msgs
* Bad string interopolation syntax
2025-08-24 13:46:47 -04:00
John Colvin 03a677df4c vscode: Add DataStreamPublisher debug cfg to skip build 2025-08-24 13:46:47 -04:00
John Colvin 07b32e4da4 Add spark service modules designed for Dependency Injection 2025-08-24 13:46:47 -04:00
John Colvin 3b1b74a1bf cleanup: Fix benign typos in build scripts
Change BUILD_SPARK_PY var name to BUILD_PY (these scripts build other
projects than 'spark').
2025-08-24 13:46:47 -04:00
John Colvin 645a447248 fix build (warn): Only use Registry on windows 2025-08-24 13:46:47 -04:00
John Colvin 4580a1af21 cleanup: Fix numerous build warnings in DataStreamSuite
Nearly all warnings were due to nullability issues, since the concept of
nullable reference types did not exist when the DataStream code was
originally written.

* PubSubClientMessage
  - Normalize indentation/whitespace
  - Fix some typos
  - Address nullability issues to fix build warnings
  - Modernize code

* Compression related files
  - ZipEntry
  - ZipFile
  - ZipInputStream
  - TarHeader

* RemoteMessage
  - make 'message' nullable to resolve warnings elsewhere (in calling code)

* PubSubSingleTagStateEvent.cs
* PubSubMessageReactor
* PubSubState
* SystemHeartbeatPublisher
* SystemStatsPublisher
* SingleTagData
* ManagedTCPMessageHub
* PubsubDeserializationController
* PublisherCore
* InflaterHuffmanTree
* AsyncTCPConnection_APM
* ThreadSafeQueue
* ManagedSubscriber
* SubscriberCore
* PubSubServerManagedTCPConnection
* AsyncTCPConnection_APM
* SuperMapStream
* MapStream
* TradeBarsStream
2025-08-24 13:46:47 -04:00
John Colvin 85a68a14c6 cleanup: Disable nullability warnings
* BZip2InputStream
* TarInputStream
* Inflater
* InflaterDynHeader
* BZip2OutputStream
* DeflaterHuffman
* ParallelJoinTest
2025-08-24 13:46:47 -04:00
John Colvin 3476bbca02 cleanup: Modernize PubSubRoute code, json parsing 2025-08-24 13:46:47 -04:00
John Colvin fdb1811f03 cleanup: Remove unused DateTime grammar from exp parser 2025-08-24 13:46:47 -04:00
John Colvin 7db9f9368b chore: Add json facilities for DataStream websocket api 2025-08-24 13:46:47 -04:00
John Colvin 983eb24584 refactor: Add handy WebsocketExtensions
* IsSocketStateOpen
* IsClosedProperly
2025-08-24 13:46:47 -04:00
John Colvin 312aa9c669 chore: Add JsonRpc types for websocket paradigm 2025-08-24 13:46:46 -04:00
John Colvin a11b0959bb cleanup: Remove empty file (clearly added by accident) 2025-08-24 13:46:46 -04:00
John Colvin 4c6702b506 editorconfig: Use standard c# capitalization rules
* Use 'PascalCase' for public members (instead of 'snake_case')
* Use 'camelCase' for private members (instead of '_snake_case')
2025-08-24 13:46:46 -04:00
John Colvin e0e0c408a5 Add generic ViewStream, BlueRow types/interfaces 2025-08-24 13:46:46 -04:00
John Colvin db2bd425be Add BlueRowColumnMask to hide columns w/ minimal overhead
Less overhead than ProjectStream
2025-08-24 13:46:46 -04:00
John Colvin 2af3c6e98d fix: Remove query from literal error stream
The parser can't handle colons in the expression (it throws/crashes)
2025-08-24 13:46:46 -04:00
John Colvin 74c1ef8dab fix: Remove erroneous "DSL: " prefix in ai prompt stream
This causes parse errors since we extended the dsl syntax to support
named params in the form of "name: arg".
2025-08-24 13:46:46 -04:00
John Colvin e3bcfa792c feat: Extend dsl to support explicit param names
When using explicit names, the argument order doesn't matter, and args
with defaults are automatically populated if omitted.
2025-08-24 13:46:46 -04:00
John Colvin 8f55916fe0 Add async SQLiteEnumerator to read sqlite tables as IBlueRowCollection 2025-08-24 13:46:46 -04:00
John Colvin 9fbf509b30 Add gen purpose blue row/table utils for data streams
* Add BlueRowReflection<T> so ViewStream implementations can use
  strong row types without needing to implement IBlueRow
* Add default implementations for IDictionary members in IBlueRow to make
  implementing IBlueRow less tedious
2025-08-24 13:46:46 -04:00
John Colvin 2004b76ebc ui: Add long/short avg cost lines to ladder 2025-08-24 13:46:46 -04:00
John Colvin 614e4de884 ui: Add exec fee columns to fills grid 2025-08-24 13:46:46 -04:00
John Colvin 0ba34da70e chore: Add exec fee to sec metadata config 2025-08-24 13:46:45 -04:00
John Colvin 826c1164be chore: Add fill_aggressor prop to ITradeLogRow 2025-08-24 13:46:45 -04:00
677 changed files with 30044 additions and 8058 deletions

No files matched your search

+6
View File
@@ -25,6 +25,12 @@
"commands": [
"fake"
]
},
"dotnet-t4": {
"version": "3.0.0",
"commands": [
"t4"
]
}
}
}
+30 -16
View File
@@ -207,17 +207,23 @@ dotnet_naming_symbols.parameters.applicable_accessibilities = public,internal,pr
dotnet_naming_symbols.private_symbols.applicable_kinds = property,method,field,event,delegate
dotnet_naming_symbols.private_symbols.applicable_accessibilities = protected,private
# local symbols
dotnet_naming_symbols.local_symbols.applicable_kinds = local
# parameter symbols
dotnet_naming_symbols.parameter_symbols.applicable_kinds = parameter
# Naming styles
dotnet_naming_style.snake_case.required_prefix =
dotnet_naming_style.snake_case.required_suffix =
dotnet_naming_style.snake_case.word_separator = _
dotnet_naming_style.snake_case.capitalization = all_lower
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.private_snake_case.required_prefix = _
dotnet_naming_style.private_snake_case.required_suffix =
dotnet_naming_style.private_snake_case.word_separator = _
dotnet_naming_style.private_snake_case.capitalization = all_lower
dotnet_naming_style.camel_case.required_prefix =
dotnet_naming_style.camel_case.required_suffix =
dotnet_naming_style.camel_case.word_separator =
dotnet_naming_style.camel_case.capitalization = camel_case
dotnet_naming_style.user_types.required_prefix =
dotnet_naming_style.user_types.required_suffix =
@@ -229,6 +235,10 @@ dotnet_naming_style.interface.required_suffix =
dotnet_naming_style.interface.word_separator =
dotnet_naming_style.interface.capitalization = pascal_case
dotnet_naming_style.local.capitalization = camel_case
dotnet_naming_style.parameter.capitalization = camel_case
# rules
dotnet_naming_rule.user_types.symbols = user_types
dotnet_naming_rule.user_types.style = user_types
@@ -238,14 +248,18 @@ dotnet_naming_rule.interfaces.symbols = interface
dotnet_naming_rule.interfaces.style = interface_style
dotnet_naming_rule.interfaces.severity = suggestion
dotnet_naming_rule.public_members_snake_case.symbols = public_symbols
dotnet_naming_rule.public_members_snake_case.style = snake_case
dotnet_naming_rule.public_members_snake_case.severity = suggestion
dotnet_naming_rule.public_members.symbols = public_symbols
dotnet_naming_rule.public_members.style = pascal_case
dotnet_naming_rule.public_members.severity = suggestion
dotnet_naming_rule.private_members_snake_case.symbols = private_symbols
dotnet_naming_rule.private_members_snake_case.style = private_snake_case
dotnet_naming_rule.private_members_snake_case.severity = suggestion
dotnet_naming_rule.private_members.symbols = private_symbols
dotnet_naming_rule.private_members.style = camel_case
dotnet_naming_rule.private_members.severity = suggestion
dotnet_naming_rule.parameters.symbols = parameters
dotnet_naming_rule.parameters.style = snake_case
dotnet_naming_rule.variables_are_camel_case.severity = suggestion
dotnet_naming_rule.variables_are_camel_case.symbols = local_symbols
dotnet_naming_rule.variables_are_camel_case.style = local
dotnet_naming_rule.parameters.symbols = parameter_symbols
dotnet_naming_rule.parameters.style = parameter
dotnet_naming_rule.parameters.severity = suggestion
+181 -22
View File
@@ -14,6 +14,68 @@
"type": "coreclr",
"request": "attach"
},
{
"name": "Api Server",
"type": "coreclr",
"request": "launch",
"program": "",
"windows": {
"preLaunchTask": "Build Api Server (Debug)",
"program": "spark_api_server.exe",
"args": [
"run",
"--sparkd",
"http://127.0.0.1:8000/config"
],
"cwd": "build/Debug/bin/net8.0",
},
"stopAtEntry": false,
"console": "integratedTerminal"
},
{
"name": "Api Server (Skip Build)",
"type": "coreclr",
"request": "launch",
"program": "",
"windows": {
"program": "spark_api_server.exe",
"args": [
"run",
"--sparkd",
"http://127.0.0.1:8000/config"
],
"cwd": "build/Debug/bin/net8.0",
},
"stopAtEntry": false,
"console": "integratedTerminal"
},
{
"name": "Bts.DataStreamWebApp.Server",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "Build DataStream WebApp (Debug)",
"program": "${workspaceFolder}/build/Debug/bin/net8.0/Bts.DataStreamWebApp.Server.dll",
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0",
"env": {
"ASPNETCORE_HTTP_PORT": "8080",
"ASPNETCORE_HTTPS_PORT": "8443",
"ASPNETCORE_ENVIRONMENT": "${input:dataStreamWebAppServerEnvironment}",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy",
}
},
{
"name": "Bts.DataStreamWebApp.Server (Skip Build)",
"type": "coreclr",
"request": "launch",
"program": "${workspaceFolder}/build/Debug/bin/net8.0/Bts.DataStreamWebApp.Server.dll",
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0",
"env": {
"ASPNETCORE_HTTP_PORT": "8080",
"ASPNETCORE_HTTPS_PORT": "8443",
"ASPNETCORE_ENVIRONMENT": "${input:dataStreamWebAppServerEnvironment}",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy",
}
},
{
"name": "DataStreamPublisher",
"type": "coreclr",
@@ -26,11 +88,28 @@
"windows": {
"program": "DataStreamPublisher.exe",
},
"args": [
"--config",
"${workspaceFolder}/src/DataStreamSuite/DataStreamPublisher/Configurations/${input:dataStreamPublisherConfig}.json"
],
"cwd": "build/Debug/bin/net8.0",
"env": {
"DOTNET_ENVIRONMENT": "${input:dataStreamPublisherEnvironment}",
},
"stopAtEntry": false,
"console": "integratedTerminal"
},
{
"name": "DataStreamPublisher (Skip Build)",
"type": "coreclr",
"request": "launch",
"program": "",
"linux": {
"program": "DataStreamPublisher",
},
"windows": {
"program": "DataStreamPublisher.exe",
},
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0",
"env": {
"DOTNET_ENVIRONMENT": "${input:dataStreamPublisherEnvironment}",
},
"stopAtEntry": false,
"console": "integratedTerminal"
},
@@ -82,16 +161,29 @@
"name": "DataStreamQueryAnalyzer",
"type": "coreclr",
"request": "launch",
//"preLaunchTask": "Build DataStreamQueryAnalyzer (Debug)",
"preLaunchTask": "Build DataStreamQueryAnalyzer (Debug)",
"program": "",
"windows": {
"program": "DataStreamQueryAnalyzer.exe",
"args": [
"--config",
"${workspaceFolder}/src/DataStreamSuite/DataStreamQueryAnalyzer/Configurations/DataStreamQueryAnalyzer.json"
],
"cwd": "build/Debug/bin/net8.0-windows",
"env": {
"DOTNET_ENVIRONMENT": "${input:dataStreamQueryAnalyzerEnvironment}",
"PYTHON_EXE": "${workspaceFolder}/.venv/Scripts/python.exe"
},
},
"stopAtEntry": false,
"console": "integratedTerminal",
},
{
"name": "DataStreamQueryAnalyzer (Skip Build)",
"type": "coreclr",
"request": "launch",
"program": "",
"windows": {
"program": "DataStreamQueryAnalyzer.exe",
"cwd": "build/Debug/bin/net8.0-windows",
"env": {
"DOTNET_ENVIRONMENT": "${input:dataStreamQueryAnalyzerEnvironment}",
"PYTHON_EXE": "${workspaceFolder}/.venv/Scripts/python.exe"
},
},
@@ -110,11 +202,28 @@
"windows": {
"program": "PubSubServer.exe",
},
"args": [
"--config",
"${workspaceFolder}/src/DataStreamSuite/PubSubServer/Configurations/${input:pubSubServerConfig}.json"
],
"cwd": "build/Debug/bin/net8.0",
"env": {
"DOTNET_ENVIRONMENT": "${input:pubSubServerEnvironment}",
},
"stopAtEntry": false,
"console": "integratedTerminal"
},
{
"name": "PubSubServer (Skip Build)",
"type": "coreclr",
"request": "launch",
"program": "",
"linux": {
"program": "PubSubServer",
},
"windows": {
"program": "PubSubServer.exe",
},
"cwd": "build/Debug/bin/net8.0",
"env": {
"DOTNET_ENVIRONMENT": "${input:pubSubServerEnvironment}",
},
"stopAtEntry": false,
"console": "integratedTerminal"
},
@@ -127,6 +236,9 @@
"preLaunchTask": "Build Spark (Debug)",
"program": "Spark.exe",
"cwd": "build/Debug/bin/net8.0-windows",
"env": {
"DOTNET_ENVIRONMENT": "${input:sparkEnvironment}",
}
},
"stopAtEntry": false,
"console": "integratedTerminal"
@@ -139,6 +251,9 @@
"windows": {
"program": "Spark.exe",
"cwd": "build/Debug/bin/net8.0-windows",
"env": {
"DOTNET_ENVIRONMENT": "${input:sparkEnvironment}",
}
},
"stopAtEntry": false,
"console": "integratedTerminal"
@@ -252,26 +367,60 @@
],
"inputs": [
{
"id": "dataStreamPublisherConfig",
"id": "dataStreamPublisherEnvironment",
"type": "pickString",
"description": "Publisher Configuration",
"options": [
"MarketDataPublisher",
"SimpleEchoClient",
"SimpleTestDataPublisher",
"TestDataPublisher",
"Development",
"MarketData",
"Production",
"ProductionAws",
"Staging",
],
"default": "MarketDataPublisher",
},
{
"id": "pubSubServerConfig",
"id": "pubSubServerEnvironment",
"type": "pickString",
"description": "PubSub Configuration",
"options": [
"PubSubServer",
"SimpleEchoClient",
"Development",
"EchoServer",
"Production",
"ProductionAws",
"Staging",
],
"default": "PubSubServer",
"default": "Development",
},
{
"id": "dataStreamWebAppServerEnvironment",
"type": "pickString",
"description": "DataStream WebApp Environment",
"options": [
"Development",
"Production",
"ProductionAws",
"Staging",
]
},
{
"id": "dataStreamQueryAnalyzerEnvironment",
"type": "pickString",
"description": "DataStreamQueryAnalyzer Configuration",
"options": [
"Development",
],
"default": "Development",
},
{
"id": "sparkEnvironment",
"type": "pickString",
"description": "Spark Configuration",
"options": [
"Development",
"Local",
],
"default": "Development",
},
{
"id": "sparkConfigServer",
@@ -289,5 +438,15 @@
"type": "promptString",
"description": "Test to run",
},
{
"id": "isSparkApiEnabled",
"type": "pickString",
"description": "Enable Spark Api (connect to sparkd)",
"options": [
"True",
"False",
],
"default": "True",
}
]
}
+255 -49
View File
@@ -121,6 +121,117 @@
"color": "terminal.ansiCyan"
},
},
{
"label": "Build DataStreamQueryAnalyzer",
"group": "build",
"type": "process",
"command": "",
"windows": {
"command": "tools/build_data_stream_query_analyzer.cmd",
},
"args": [
"--configuration",
"${input:buildConfiguration}",
],
"icon": {
"id": "terminal",
"color": "terminal.ansiCyan"
},
},
{
"label": "Build DataStreamQueryAnalyzer (Debug)",
"group": "build",
"type": "process",
"command": "",
"windows": {
"command": "tools/build_data_stream_query_analyzer.cmd",
},
"args": [
"--configuration",
"Debug",
],
"icon": {
"id": "terminal",
"color": "terminal.ansiCyan"
},
},
{
"label": "Build DataStreamQueryAnalyzer (Release)",
"group": "build",
"type": "process",
"command": "",
"windows": {
"command": "tools/build_data_stream_query_analyzer.cmd",
},
"args": [
"--configuration",
"Release",
],
"icon": {
"id": "terminal",
"color": "terminal.ansiCyan"
},
},
{
"label": "Build DataStream WebApp",
"group": "build",
"type": "process",
"command": "",
"linux": {
"command": "tools/build_data_stream_web_app.sh",
},
"windows": {
"command": "tools/build_data_stream_web_app.cmd",
},
"args": [
"--configuration",
"${input:buildConfiguration}",
],
"icon": {
"id": "terminal",
"color": "terminal.ansiCyan"
},
},
{
"label": "Build DataStream WebApp (Debug)",
"group": "build",
"type": "process",
"command": "",
"linux": {
"command": "tools/build_data_stream_web_app.sh",
},
"windows": {
"command": "tools/build_data_stream_web_app.cmd",
},
"args": [
"--configuration",
"Debug",
],
"icon": {
"id": "terminal",
"color": "terminal.ansiCyan"
},
},
{
"label": "Build DataStream WebApp (Release)",
"group": "build",
"type": "process",
"command": "",
"linux": {
"command": "tools/build_data_stream_web_app.sh",
},
"windows": {
"command": "tools/build_data_stream_web_app.cmd",
},
"args": [
"--configuration",
"Release",
],
"icon": {
"id": "terminal",
"color": "terminal.ansiCyan"
},
},
{
"label": "Build Spark",
"group": "build",
@@ -303,6 +414,22 @@
"color": "terminal.ansiCyan"
},
},
{
"label": "Publish DataStreamWebApp",
"group": "build",
"type": "process",
"command": "",
"linux": {
"command": "tools/publish_data_stream_web_app.sh",
},
"windows": {
"command": "tools/publish_data_stream_web_app.cmd",
},
"icon": {
"id": "terminal",
"color": "terminal.ansiCyan"
},
},
{
"label": "Edit DataStream Config",
"type": "process",
@@ -353,7 +480,7 @@
},
{
"label": "Run DataStreamQueryAnalyzer",
"dependsOn": "Build DataStream",
"dependsOn": "Build DataStreamQueryAnalyzer",
"type": "process",
"command": "",
"windows": {
@@ -373,7 +500,7 @@
},
{
"label": "Run DataStreamQueryAnalyzer (Debug)",
"dependsOn": "Build DataStream (Debug)",
"dependsOn": "Build DataStreamQueryAnalyzer (Debug)",
"type": "process",
"command": "",
"windows": {
@@ -393,7 +520,7 @@
},
{
"label": "Run DataStreamQueryAnalyzer (Release)",
"dependsOn": "Build DataStream (Release)",
"dependsOn": "Build DataStreamQueryAnalyzer (Release)",
"type": "process",
"command": "",
"windows": {
@@ -422,12 +549,11 @@
"windows": {
"command": "DataStreamPublisher.exe",
},
"args": [
"--config",
"${workspaceFolder}/src/DataStreamSuite/DataStreamPublisher/Configurations/${input:dataStreamPublisherConfig}.json",
],
"options": {
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net8.0"
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net8.0",
"env": {
"DOTNET_ENVIRONMENT": "${input:dataStreamPublisherEnvironment}",
},
},
"icon": {
"id": "run",
@@ -445,12 +571,11 @@
"windows": {
"command": "DataStreamPublisher.exe",
},
"args": [
"--config",
"${workspaceFolder}/src/DataStreamSuite/DataStreamPublisher/Configurations/${input:dataStreamPublisherConfig}.json",
],
"options": {
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0"
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0",
"env": {
"DOTNET_ENVIRONMENT": "${input:dataStreamPublisherEnvironment}",
},
},
"icon": {
"id": "run",
@@ -468,12 +593,94 @@
"windows": {
"command": "DataStreamPublisher.exe",
},
"options": {
"cwd": "${workspaceFolder}/build/Release/bin/net8.0",
"env": {
"DOTNET_ENVIRONMENT": "${input:dataStreamPublisherEnvironment}",
},
},
"icon": {
"id": "run",
"color": "terminal.ansiGreen",
},
},
{
"label": "Run DataStream WebApp (Debug)",
"dependsOn": "Build DataStream WebApp (Debug)",
"command": "",
"linux": {
"command": "Bts.DataStreamWebApp.Server",
},
"windows": {
"command": "Bts.DataStreamWebApp.Server.exe",
},
"args": [
"--config",
"${workspaceFolder}/src/DataStreamSuite/DataStreamPublisher/Configurations/${input:dataStreamPublisherConfig}.json",
"${workspaceFolder}/src/DataStreamSuite/Bts.DataStreamWebApp.Server/Configurations/DevServer.json",
],
"options": {
"cwd": "${workspaceFolder}/build/Release/bin/net8.0"
"cwd": "${workspaceFolder}/build/Debug/bin/net7.0",
"env": {
"ASPNETCORE_HTTP_PORT": "8080",
"ASPNETCORE_HTTPS_PORT": "8443",
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy",
}
},
"icon": {
"id": "run",
"color": "terminal.ansiGreen",
},
},
{
"label": "Run DataStream WebApp (Release)",
"dependsOn": "Build DataStream WebApp (Release)",
"command": "",
"linux": {
"command": "Bts.DataStreamWebApp.Server",
},
"windows": {
"command": "Bts.DataStreamWebApp.Server.exe",
},
"args": [
"--config",
"${workspaceFolder}/src/DataStreamSuite/Bts.DataStreamWebApp.Server/Configurations/DevServer.json",
],
"options": {
"cwd": "${workspaceFolder}/build/Release/bin/net7.0",
"env": {
"ASPNETCORE_HTTP_PORT": "8080",
"ASPNETCORE_HTTPS_PORT": "8443",
"ASPNETCORE_ENVIRONMENT": "Production",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy",
}
},
"icon": {
"id": "run",
"color": "terminal.ansiGreen",
},
},
{
"label": "Run DataStream WebApp (Release | Skip Build)",
"command": "",
"linux": {
"command": "Bts.DataStreamWebApp.Server",
},
"windows": {
"command": "Bts.DataStreamWebApp.Server.exe",
},
"args": [
"--config",
"${workspaceFolder}/src/DataStreamSuite/Bts.DataStreamWebApp.Server/Configurations/DevServer.json",
],
"options": {
"cwd": "${workspaceFolder}/build/Release/bin/net7.0",
"env": {
"ASPNETCORE_HTTP_PORT": "8080",
"ASPNETCORE_HTTPS_PORT": "8443",
"ASPNETCORE_ENVIRONMENT": "Production",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy",
}
},
"icon": {
"id": "run",
@@ -491,12 +698,11 @@
"windows": {
"command": "PubSubServer.exe",
},
"args": [
"--config",
"${workspaceFolder}/src/DataStreamSuite/PubSubServer/Configurations/${input:pubSubServerConfig}.json",
],
"options": {
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net8.0"
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net8.0",
"env": {
"DOTNET_ENVIRONMENT": "${input:pubSubServerEnvironment}",
},
},
"icon": {
"id": "run",
@@ -514,12 +720,11 @@
"windows": {
"command": "PubSubServer.exe",
},
"args": [
"--config",
"${workspaceFolder}/src/DataStreamSuite/PubSubServer/Configurations/${input:pubSubServerConfig}.json",
],
"options": {
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0"
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0",
"env": {
"DOTNET_ENVIRONMENT": "${input:pubSubServerEnvironment}",
},
},
"icon": {
"id": "run",
@@ -536,12 +741,11 @@
"windows": {
"command": "PubSubServer.exe",
},
"args": [
"--config",
"${workspaceFolder}/src/DataStreamSuite/PubSubServer/Configurations/${input:pubSubServerConfig}.json",
],
"options": {
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0"
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0",
"env": {
"DOTNET_ENVIRONMENT": "${input:pubSubServerEnvironment}",
},
},
"icon": {
"id": "run",
@@ -559,12 +763,11 @@
"windows": {
"command": "PubSubServer.exe",
},
"args": [
"--config",
"${workspaceFolder}/src/DataStreamSuite/PubSubServer/Configurations/${input:pubSubServerConfig}.json",
],
"options": {
"cwd": "${workspaceFolder}/build/Release/bin/net8.0"
"cwd": "${workspaceFolder}/build/Release/bin/net8.0",
"env": {
"DOTNET_ENVIRONMENT": "${input:pubSubServerEnvironment}",
},
},
"icon": {
"id": "run",
@@ -581,12 +784,11 @@
"windows": {
"command": "PubSubServer.exe",
},
"args": [
"--config",
"${workspaceFolder}/src/DataStreamSuite/PubSubServer/Configurations/${input:pubSubServerConfig}.json",
],
"options": {
"cwd": "${workspaceFolder}/build/Release/bin/net8.0"
"cwd": "${workspaceFolder}/build/Release/bin/net8.0",
"env": {
"DOTNET_ENVIRONMENT": "${input:pubSubServerEnvironment}",
},
},
"icon": {
"id": "run",
@@ -885,26 +1087,30 @@
"default": "sparkd-debug-clang",
},
{
"id": "dataStreamPublisherConfig",
"id": "dataStreamPublisherEnvironment",
"type": "pickString",
"description": "Publisher Configuration",
"options": [
"MarketDataPublisher",
"SimpleEchoClient",
"SimpleTestDataPublisher",
"TestDataPublisher",
"Development",
"MarketData",
"Production",
"ProductionAws",
"Staging",
],
"default": "MarketDataPublisher",
},
{
"id": "pubSubServerConfig",
"id": "pubSubServerEnvironment",
"type": "pickString",
"description": "PubSub Configuration",
"options": [
"PubSubServer",
"SimpleEchoClient",
"Development",
"EchoServer",
"Production",
"ProductionAws",
"Staging",
],
"default": "PubSubServer",
"default": "Development",
},
{
"id": "sparkConfigQuery",
+3 -4
View File
@@ -21,7 +21,7 @@ set(
# SHARED_MEMORY_PREFIX serves as a kind of namespace so multiple applications
# can run simultaneously without fighting over the same shm files (e.g., sparkd,
# api server, etc.).
set(SHARED_MEMORY_PREFIX "bts" CACHE STRING "Prefix to use when creating shared memory files")
set(SHARED_MEMORY_PREFIX "\${BTS_SHARED_MEMORY_PREFIX}" CACHE STRING "Prefix to use when creating shared memory files")
set(EXTERNALS_BIN "${EXTERNALS_ROOT}/bin")
set(EXTERNALS_INCLUDE "${EXTERNALS_ROOT}/include")
set(EXTERNALS_LIB "${EXTERNALS_ROOT}/lib/$<CONFIG>")
@@ -40,9 +40,8 @@ include_directories(BEFORE SYSTEM ${EXTERNALS_INCLUDE} ${EXTERNALS_ROOT}/include
link_directories(${EXTERNALS_LIB})
add_compile_definitions(SHARED_MEMORY_PREFIX="${SHARED_MEMORY_PREFIX}")
add_compile_definitions(APP_DATA_DIR="${APP_DATA_DIR}")
if(DEFINED APP_DATA_DIR_OVERRIDE)
add_compile_definitions(APP_DATA_DIR_OVERRIDE="${APP_DATA_DIR_OVERRIDE}")
if(DEFINED APP_DATA_DIR)
add_compile_definitions(APP_DATA_DIR="${APP_DATA_DIR}")
endif()
add_compile_definitions(EXPORT_MD_CONSUMER="${EXPORT_MD_CONSUMER}")
if(WIN32)
+6 -22
View File
@@ -20,8 +20,6 @@
"SPARK_CMAKE_CXX_FLAGS": "/MP /WX /wd4250 /wd4251 /wd4244 /wd4267 /wd4355 /wd4275 /wd4345 /bigobj /EHsc /DCURL_STATICLIB /D_WINSOCK_DEPRECATED_NO_WARNINGS /D__TBB_NO_IMPLICIT_LINKAGE /DBOOST_ALL_NO_LIB /std:c++20 /D_SCL_SECURE_NO_WARNINGS /D_CRT_SECURE_NO_WARNINGS /D_WIN32_WINNT=0x0600"
},
"cacheVariables": {
"APP_DATA_DIR": "${dollar}{APPDATA}/bts",
"APP_DATA_DIR_OVERRIDE": "${dollar}{SPARK_DATA_DIR}",
"CMAKE_CXX_FLAGS": "$env{SPARK_CMAKE_CXX_FLAGS}",
"CMAKE_CXX_FLAGS_DEBUG": "$env{SPARK_CMAKE_CXX_FLAGS} /MTd",
"CMAKE_CXX_FLAGS_RELEASE": "$env{SPARK_CMAKE_CXX_FLAGS} /MT",
@@ -36,8 +34,7 @@
"EXPORT_MD_CONSUMER": {
"type": "BOOL",
"value": "ON"
},
"SHARED_MEMORY_PREFIX": "spark"
}
}
},
@@ -53,8 +50,6 @@
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"platform": "",
"APP_DATA_DIR": "${dollar}{HOME}/sparkdata",
"APP_DATA_DIR_OVERRIDE": "${dollar}{SPARKD_DATA_DIR}",
"CMAKE_BUILD_WITH_INSTALL_RPATH": {
"type": "BOOL",
"value": "OFF"
@@ -77,8 +72,7 @@
"BUILD_ADMIN_WEB_APP": {
"type": "BOOL",
"value": "ON"
},
"SHARED_MEMORY_PREFIX": "sparkd"
}
}
},
@@ -159,11 +153,7 @@
{
"name": "spark-api-server-windows",
"inherits": "spark",
"displayName": "Spark Api Server (Windows)",
"cacheVariables": {
"APP_DATA_DIR_OVERRIDE": "${dollar}{SPARK_API_SERVER_DATA_DIR}",
"SHARED_MEMORY_PREFIX": "spark_api_server"
}
"displayName": "Spark Api Server (Windows)"
},
{
@@ -172,7 +162,6 @@
"displayName": "Spark Api Server (Linux)",
"generator": "Ninja Multi-Config",
"cacheVariables": {
"APP_DATA_DIR_OVERRIDE": "${dollar}{SPARK_API_SERVER_DATA_DIR}",
"BUILD_ADMIN_WEB_APP": {
"type": "BOOL",
"value": "OFF"
@@ -191,7 +180,7 @@
"value": "ON"
},
"CONFIG_DEFAULTS_PATH": "spark/config/profiles/default/*.conf",
"DOTNET_BINARY_DIR": "${sourceDir}/build/$<CONFIG>/bin/net8.0",
"DOTNET_BINARY_DIR": "${sourceDir}/build/Spark.Api.Server/$<CONFIG>/bin/net8.0",
"EXPORT_MD_CONSUMER": {
"type": "BOOL",
"value": "ON"
@@ -229,10 +218,8 @@
"description": "Data Stream utils ported over from Edge",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"APP_DATA_DIR": "${dollar}{APPDATA}/bts/DataStream",
"APP_DATA_DIR_OVERRIDE": "${dollar}{DATA_STREAM_DATA_DIR}",
"CONFIG_DEFAULTS_PATH": "spark/config/profiles/default/*.conf;spark/config/profiles/data_stream/*.conf",
"SHARED_MEMORY_PREFIX": "data_stream"
"DOTNET_BINARY_DIR": "${sourceDir}/build/$<CONFIG>/bin/net8.0;${sourceDir}/build/$<CONFIG>/bin/net8.0-windows"
}
},
@@ -248,8 +235,6 @@
"inherits": "sparkd",
"generator": "Ninja Multi-Config",
"cacheVariables": {
"APP_DATA_DIR": "${dollar}{HOME}/.local/share/DataStream",
"APP_DATA_DIR_OVERRIDE": "${dollar}{DATA_STREAM_DATA_DIR}",
"BUILD_ADMIN_WEB_APP": {
"type": "BOOL",
"value": "OFF"
@@ -271,8 +256,7 @@
"EXPORT_MD_CONSUMER": {
"type": "BOOL",
"value": "ON"
},
"SHARED_MEMORY_PREFIX": "data_stream"
}
}
}
],
+69 -51
View File
@@ -29,36 +29,43 @@ function(Protobuf_Generate_Cpp)
list(APPEND ${_ARGS_CXX_HEADERS} "${_ARGS_CXX_OUT_DIR}/${FIL_WE}.pb.h")
add_custom_command(
OUTPUT "${_ARGS_CXX_OUT_DIR}/${FIL_WE}.pb.cc"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.pb.h"
OUTPUT
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.pb.cc"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.pb.h"
COMMAND
${Protobuf_PROTOC_EXECUTABLE} ARGS --cpp_out=${_ARGS_CXX_OUT_DIR} ${_proto_include_cli_args} ${ABS_FIL}
${Protobuf_PROTOC_EXECUTABLE}
ARGS
--cpp_out=${_ARGS_CXX_OUT_DIR} ${_proto_include_cli_args} ${ABS_FIL}
DEPENDS ${ABS_FIL}
COMMENT "Running C++ protocol buffer compiler on ${FIL}"
VERBATIM)
VERBATIM
)
endforeach()
set_source_files_properties(${${_ARGS_CXX_SOURCES}} ${${_ARGS_CXX_HEADERS}} PROPERTIES GENERATED TRUE)
set_source_files_properties(
${${_ARGS_CXX_SOURCES}}
${${_ARGS_CXX_HEADERS}}
PROPERTIES
GENERATED TRUE
)
# remove some warnings from protobuf generated files
if(WIN32)
set_source_files_properties(${${_ARGS_CXX_SOURCES}} PROPERTIES COMPILE_FLAGS
"/wd4309 /wd4838")
set_source_files_properties(
${${_ARGS_CXX_SOURCES}}
PROPERTIES
COMPILE_FLAGS "/wd4309 /wd4838"
)
else()
set_source_files_properties(
${${_ARGS_CXX_SOURCES}}
PROPERTIES
COMPILE_FLAGS
"-Wno-unused-parameter -Wno-effc++ -Wno-pedantic -Wno-sign-compare -Wno-uninitialized"
COMPILE_FLAGS "-Wno-unused-parameter -Wno-effc++ -Wno-pedantic -Wno-sign-compare -Wno-uninitialized"
)
endif()
set(${_ARGS_CXX_SOURCES}
${${_ARGS_CXX_SOURCES}}
PARENT_SCOPE)
set(${_ARGS_CXX_HEADERS}
${${_ARGS_CXX_HEADERS}}
PARENT_SCOPE)
set(${_ARGS_CXX_SOURCES} ${${_ARGS_CXX_SOURCES}} PARENT_SCOPE)
set(${_ARGS_CXX_HEADERS} ${${_ARGS_CXX_HEADERS}} PARENT_SCOPE)
endfunction()
function(Service_Compiler_Generate_Cpp)
@@ -92,40 +99,51 @@ function(Service_Compiler_Generate_Cpp)
list(APPEND ${_ARGS_CXX_HEADERS} "${_ARGS_CXX_OUT_DIR}/${FIL_WE}.server.pb.h")
set(TEMPLATES
${CMAKE_SOURCE_DIR}/src/lib_spark/spark/api/service_compiler/templates/cpp/client.pb.cc
${CMAKE_SOURCE_DIR}/src/lib_spark/spark/api/service_compiler/templates/cpp/client.pb.h
${CMAKE_SOURCE_DIR}/src/lib_spark/spark/api/service_compiler/templates/cpp/server.pb.cc
${CMAKE_SOURCE_DIR}/src/lib_spark/spark/api/service_compiler/templates/cpp/server.pb.h
${CMAKE_SOURCE_DIR}/src/lib_spark/spark/api/service_compiler/templates/cpp/client.pb.cc
${CMAKE_SOURCE_DIR}/src/lib_spark/spark/api/service_compiler/templates/cpp/client.pb.h
${CMAKE_SOURCE_DIR}/src/lib_spark/spark/api/service_compiler/templates/cpp/server.pb.cc
${CMAKE_SOURCE_DIR}/src/lib_spark/spark/api/service_compiler/templates/cpp/server.pb.h
)
set(_plugin_py ${CMAKE_SOURCE_DIR}/src/lib_spark/spark/api/service_compiler/plugin.py)
if(WIN32)
set(_plugin_cmd ${CMAKE_SOURCE_DIR}/src/lib_spark/spark/api/service_compiler/plugin.cmd)
add_custom_command(
OUTPUT "${_ARGS_CXX_OUT_DIR}/${FIL_WE}.pb.cc"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.pb.h"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.client.pb.cc"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.client.pb.h"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.server.pb.cc"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.server.pb.h"
OUTPUT
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.pb.cc"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.pb.h"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.client.pb.cc"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.client.pb.h"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.server.pb.cc"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.server.pb.h"
COMMAND SET PYTHONPATH=${SPARK_PYTHON}
COMMAND ${CMAKE_SOURCE_DIR}/.venv/Scripts/activate.bat
COMMAND
${Protobuf_PROTOC_EXECUTABLE} ARGS --cpp_out=${_ARGS_CXX_OUT_DIR}
--plugin=protoc-gen-service=${_plugin_cmd}
--service_out=lang=cpp:${_ARGS_CXX_OUT_DIR} -I
${PROTOBUF_INCLUDE_DIRS} -I ${_protobuf_src_dir} ${ABS_FIL}
DEPENDS ${ABS_FIL} ${TEMPLATES} ${_plugin_cmd} ${_plugin_py}
${Protobuf_PROTOC_EXECUTABLE}
ARGS
--cpp_out=${_ARGS_CXX_OUT_DIR}
--plugin=protoc-gen-service=${_plugin_cmd}
--service_out=lang=cpp:${_ARGS_CXX_OUT_DIR}
-I ${PROTOBUF_INCLUDE_DIRS}
-I ${_protobuf_src_dir}
${ABS_FIL}
DEPENDS
${ABS_FIL}
${TEMPLATES}
${_plugin_cmd}
${_plugin_py}
COMMENT "Running C++ service compiler on ${FIL}"
VERBATIM)
VERBATIM
)
else()
add_custom_command(
OUTPUT "${_ARGS_CXX_OUT_DIR}/${FIL_WE}.pb.cc"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.pb.h"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.client.pb.cc"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.client.pb.h"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.server.pb.cc"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.server.pb.h"
OUTPUT
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.pb.cc"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.pb.h"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.client.pb.cc"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.client.pb.h"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.server.pb.cc"
"${_ARGS_CXX_OUT_DIR}/${FIL_WE}.server.pb.h"
COMMAND ${CMAKE_SOURCE_DIR}/tools/venv_exec.sh
ARGS
${Protobuf_PROTOC_EXECUTABLE}
@@ -133,32 +151,32 @@ function(Service_Compiler_Generate_Cpp)
--plugin=protoc-gen-service=${_plugin_py}
--service_out=lang=cpp,make_file_name=${ABS_FIL}:${_ARGS_CXX_OUT_DIR}
${_proto_include_cli_args} ${ABS_FIL}
DEPENDS ${ABS_FIL} ${TEMPLATES} ${_plugin_py}
DEPENDS
${ABS_FIL}
${TEMPLATES}
${_plugin_py}
COMMENT "Running C++ service compiler on ${FIL}"
VERBATIM)
VERBATIM
)
endif()
endforeach()
# remove some warnings from protobuf generated files
if(WIN32)
set_source_files_properties(${${_ARGS_CXX_SOURCES}} PROPERTIES COMPILE_FLAGS
"/wd4309 /wd4838")
set_source_files_properties(
${${_ARGS_CXX_SOURCES}}
PROPERTIES
COMPILE_FLAGS "/wd4309 /wd4838"
)
else()
set_source_files_properties(
${${_ARGS_CXX_SOURCES}}
PROPERTIES
COMPILE_FLAGS
"-Wno-unused-parameter -Wno-effc++ -Wno-pedantic -Wno-sign-compare -Wno-uninitialized"
COMPILE_FLAGS "-Wno-unused-parameter -Wno-effc++ -Wno-pedantic -Wno-sign-compare -Wno-uninitialized"
)
endif()
set(${_ARGS_CXX_SOURCES}
${${_ARGS_CXX_SOURCES}}
PARENT_SCOPE)
set(${_ARGS_CXX_HEADERS}
${${_ARGS_CXX_HEADERS}}
PARENT_SCOPE)
set(${_ARGS_CXX_SOURCES} ${${_ARGS_CXX_SOURCES}} PARENT_SCOPE)
set(${_ARGS_CXX_HEADERS} ${${_ARGS_CXX_HEADERS}} PARENT_SCOPE)
endfunction()
+49
View File
@@ -109,6 +109,55 @@ services:
RUN apt-get update && apt-get -y install iputils-ping
command: [ "ping", "sparkd" ]
pubsub:
environment:
- DOTNET_ENVIRONMENT=Staging
build:
dockerfile: src/DataStreamSuite/PubSubServer/Dockerfile
args:
- SOURCE_DIR=build/Release/bin/net8.0/linux-x64/publish
- CONF_FILE=src/DataStreamSuite/PubSubServer/spark.conf
ports:
- 22019:22019
command: [ "./PubSubServer" ]
md-publisher:
build:
dockerfile: src/DataStreamSuite/DataStreamPublisher/Dockerfile
args:
- SOURCE_DIR=build/Release/bin/net8.0/linux-x64/publish
- CONF_FILE=src/DataStreamSuite/DataStreamPublisher/spark.conf
depends_on:
- pubsub
- sparkd
command: [ "dockerize", "-wait", "tcp://pubsub:22019", "-wait", "tcp://sparkd:8000", "-timeout", "10s", "./DataStreamPublisher" ]
environment:
- DOTNET_ENVIRONMENT=MarketData
api-server:
depends_on:
- sparkd
ports:
- 8008:8008
- 9443:9443
build:
dockerfile: src/client/spark_api_server/Dockerfile
command: [ "dockerize", "-wait", "tcp://sparkd:8000", "-timeout", "10s", "./spark_api_server", "run" ]
webapp:
ports:
- 8080:8080
- 8443:8443
build:
dockerfile: src/DataStreamSuite/Bts.DataStreamWebApp.Server/Dockerfile
args:
- SOURCE_DIR=build/Release/bin/net8.0/linux-x64/publish
- WEB_ROOT=build/Bts.DataStreamWebApp.Client/production/dist
- CONF_FILE=src/DataStreamSuite/Bts.DataStreamWebApp.Server/spark.conf
command: [ "./Bts.DataStreamWebApp.Server" ]
environment:
- ASPNETCORE_ENVIRONMENT=Staging
volumes:
sparkd_data:
name: "sparkd_${SPARKD__CONFIG_PORT:-8000}"
+39
View File
@@ -0,0 +1,39 @@
FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y pkg-config \
&& apt-get install -y locales \
&& apt-get install -y ca-certificates \
&& apt-get install -y libssh2-1-dev \
&& apt-get install -y software-properties-common \
&& apt-get install -y sudo
# GCC
ARG gcc_major=13
RUN add-apt-repository ppa:ubuntu-toolchain-r/test \
&& apt-get update \
&& apt-get install -y g++-$gcc_major
RUN apt-get update \
&& apt-get install -y dotnet7
RUN locale-gen en_US.UTF-8
ARG docker_user="bts"
ARG docker_uid="1000"
ARG docker_pass="bts"
RUN \
groupadd -g "$docker_uid" "$docker_user" \
&& useradd \
-m \
-u "$docker_uid" \
-g "$docker_uid" \
-G sudo \
--password "$(openssl passwd -1 "$docker_pass")" \
"$docker_user"
USER bts
WORKDIR /home/bts
-6
View File
@@ -1,6 +0,0 @@
{
"sdk": {
"version": "6.0.200",
"rollForward": "latestMajor"
}
}
+164 -143
View File
@@ -1,143 +1,164 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32922.545
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_api", "src\client\spark_api\spark_api.csproj", "{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark", "src\client\spark\Spark.csproj", "{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark.Console", "src\client\Spark.Console\Spark.Console.csproj", "{387566C6-C6F6-412C-A30C-9855BBCB4816}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_core", "src\client\spark_core\spark_core.csproj", "{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_sdk", "src\client\spark_sdk\spark_sdk.csproj", "{6963C314-83C4-4667-B0D0-C2F7AE208C29}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\spark_test\spark_test.csproj", "{ECD78CC5-138F-4809-A764-2358C2AC2E82}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_websocket", "src\client\spark_websocket\spark_websocket.csproj", "{3201359D-C071-4089-9EDE-B04D759F1508}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_api_server", "src\client\spark_api_server\spark_api_server.csproj", "{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStreamCore", "src\DataStreamSuite\DataStreamCore\DataStreamCore.csproj", "{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStream", "src\DataStreamSuite\DataStream\DataStream.csproj", "{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedStreams", "src\DataStreamSuite\SharedStreams\SharedStreams.csproj", "{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PubSub", "src\DataStreamSuite\PubSub\PubSub.csproj", "{51C40BA9-F226-4A35-939D-1ED6F5FF7299}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
MinSizeRel|x64 = MinSizeRel|x64
Release|x64 = Release|x64
RelWithDebInfo|x64 = RelWithDebInfo|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x64.ActiveCfg = Debug|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x64.Build.0 = Debug|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x64.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x64.Build.0 = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x64.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x64.Build.0 = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.ActiveCfg = Debug|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.Build.0 = Debug|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x64.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x64.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x64.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x64.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x64.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.ActiveCfg = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.Build.0 = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.ActiveCfg = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.Build.0 = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x64.ActiveCfg = Debug|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x64.Build.0 = Debug|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x64.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x64.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x64.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x64.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x64.Build.0 = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x64.ActiveCfg = Debug|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x64.Build.0 = Debug|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x64.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x64.Build.0 = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|x64.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|x64.Build.0 = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x64.Build.0 = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x64.ActiveCfg = Debug|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x64.Build.0 = Debug|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x64.ActiveCfg = Debug|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x64.Build.0 = Debug|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.ActiveCfg = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.Build.0 = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.Build.0 = Release|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.Debug|x64.ActiveCfg = Debug|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.Debug|x64.Build.0 = Debug|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.MinSizeRel|x64.ActiveCfg = Debug|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.MinSizeRel|x64.Build.0 = Debug|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.Release|x64.ActiveCfg = Release|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.Release|x64.Build.0 = Release|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.RelWithDebInfo|x64.Build.0 = Release|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.Debug|x64.ActiveCfg = Debug|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.Debug|x64.Build.0 = Debug|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.MinSizeRel|x64.ActiveCfg = Debug|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.MinSizeRel|x64.Build.0 = Debug|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.Release|x64.ActiveCfg = Release|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.Release|x64.Build.0 = Release|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.RelWithDebInfo|x64.Build.0 = Release|x64
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.Debug|x64.ActiveCfg = Debug|Any CPU
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.Debug|x64.Build.0 = Debug|Any CPU
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.Release|x64.ActiveCfg = Release|Any CPU
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.Release|x64.Build.0 = Release|Any CPU
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.Debug|x64.ActiveCfg = Debug|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.Debug|x64.Build.0 = Debug|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.Release|x64.ActiveCfg = Release|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.Release|x64.Build.0 = Release|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.Debug|x64.ActiveCfg = Debug|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.Debug|x64.Build.0 = Debug|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.Release|x64.ActiveCfg = Release|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.Release|x64.Build.0 = Release|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.Debug|x64.ActiveCfg = Debug|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.Debug|x64.Build.0 = Debug|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.Release|x64.ActiveCfg = Release|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.Release|x64.Build.0 = Release|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {422D1620-0166-4C26-9A81-CA8A2EB1AB58}
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32922.545
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_api", "src\client\spark_api\spark_api.csproj", "{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark", "src\client\spark\Spark.csproj", "{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark.Console", "src\client\Spark.Console\Spark.Console.csproj", "{387566C6-C6F6-412C-A30C-9855BBCB4816}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_core", "src\client\spark_core\spark_core.csproj", "{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_sdk", "src\client\spark_sdk\spark_sdk.csproj", "{6963C314-83C4-4667-B0D0-C2F7AE208C29}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\spark_test\spark_test.csproj", "{ECD78CC5-138F-4809-A764-2358C2AC2E82}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_websocket", "src\client\spark_websocket\spark_websocket.csproj", "{3201359D-C071-4089-9EDE-B04D759F1508}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_api_server", "src\client\spark_api_server\spark_api_server.csproj", "{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStreamCore", "src\DataStreamSuite\DataStreamCore\DataStreamCore.csproj", "{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStream", "src\DataStreamSuite\DataStream\DataStream.csproj", "{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedStreams", "src\DataStreamSuite\SharedStreams\SharedStreams.csproj", "{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PubSub", "src\DataStreamSuite\PubSub\PubSub.csproj", "{51C40BA9-F226-4A35-939D-1ED6F5FF7299}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4564E677-343A-46CB-AF02-AFC4E38BF94B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "client", "client", "{56A8A70A-DA03-4CED-A144-EC10F1379FD8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataStreamSuite", "DataStreamSuite", "{915EF238-CA0D-4E73-AF50-88CB3F29C7AD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.Extensions.Hosting", "src\DataStreamSuite\Bts.Extensions.Hosting\Bts.Extensions.Hosting.csproj", "{4E0E51AA-7B59-4808-B7CC-9EAEDF397460}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
MinSizeRel|x64 = MinSizeRel|x64
Release|x64 = Release|x64
RelWithDebInfo|x64 = RelWithDebInfo|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x64.ActiveCfg = Debug|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x64.Build.0 = Debug|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x64.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x64.Build.0 = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x64.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x64.Build.0 = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.ActiveCfg = Debug|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.Build.0 = Debug|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x64.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x64.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x64.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x64.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x64.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.ActiveCfg = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.Build.0 = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.ActiveCfg = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.Build.0 = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x64.ActiveCfg = Debug|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x64.Build.0 = Debug|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x64.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x64.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x64.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x64.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x64.Build.0 = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x64.ActiveCfg = Debug|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x64.Build.0 = Debug|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x64.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x64.Build.0 = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|x64.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|x64.Build.0 = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x64.Build.0 = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x64.ActiveCfg = Debug|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x64.Build.0 = Debug|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x64.ActiveCfg = Debug|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x64.Build.0 = Debug|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.ActiveCfg = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.Build.0 = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.Build.0 = Release|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.Debug|x64.ActiveCfg = Debug|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.Debug|x64.Build.0 = Debug|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.MinSizeRel|x64.ActiveCfg = Debug|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.MinSizeRel|x64.Build.0 = Debug|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.Release|x64.ActiveCfg = Release|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.Release|x64.Build.0 = Release|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{3201359D-C071-4089-9EDE-B04D759F1508}.RelWithDebInfo|x64.Build.0 = Release|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.Debug|x64.ActiveCfg = Debug|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.Debug|x64.Build.0 = Debug|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.MinSizeRel|x64.ActiveCfg = Debug|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.MinSizeRel|x64.Build.0 = Debug|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.Release|x64.ActiveCfg = Release|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.Release|x64.Build.0 = Release|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.RelWithDebInfo|x64.Build.0 = Release|x64
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.Debug|x64.ActiveCfg = Debug|Any CPU
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.Debug|x64.Build.0 = Debug|Any CPU
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.Release|x64.ActiveCfg = Release|Any CPU
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.Release|x64.Build.0 = Release|Any CPU
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{4E4F491F-C688-43C4-88DA-AC92BC5FA1B8}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.Debug|x64.ActiveCfg = Debug|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.Debug|x64.Build.0 = Debug|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.Release|x64.ActiveCfg = Release|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.Release|x64.Build.0 = Release|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{2AC54ADC-3246-427E-9CA9-FEA2DBCB9432}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.Debug|x64.ActiveCfg = Debug|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.Debug|x64.Build.0 = Debug|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.Release|x64.ActiveCfg = Release|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.Release|x64.Build.0 = Release|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{4E96E5CC-7791-40B3-A8A1-C34FBB736B3B}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.Debug|x64.ActiveCfg = Debug|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.Debug|x64.Build.0 = Debug|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.Release|x64.ActiveCfg = Release|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.Release|x64.Build.0 = Release|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{51C40BA9-F226-4A35-939D-1ED6F5FF7299}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{4E0E51AA-7B59-4808-B7CC-9EAEDF397460}.Debug|x64.ActiveCfg = Debug|Any CPU
{4E0E51AA-7B59-4808-B7CC-9EAEDF397460}.Debug|x64.Build.0 = Debug|Any CPU
{4E0E51AA-7B59-4808-B7CC-9EAEDF397460}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{4E0E51AA-7B59-4808-B7CC-9EAEDF397460}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{4E0E51AA-7B59-4808-B7CC-9EAEDF397460}.Release|x64.ActiveCfg = Release|Any CPU
{4E0E51AA-7B59-4808-B7CC-9EAEDF397460}.Release|x64.Build.0 = Release|Any CPU
{4E0E51AA-7B59-4808-B7CC-9EAEDF397460}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
{4E0E51AA-7B59-4808-B7CC-9EAEDF397460}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {422D1620-0166-4C26-9A81-CA8A2EB1AB58}
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{56A8A70A-DA03-4CED-A144-EC10F1379FD8} = {4564E677-343A-46CB-AF02-AFC4E38BF94B}
{915EF238-CA0D-4E73-AF50-88CB3F29C7AD} = {4564E677-343A-46CB-AF02-AFC4E38BF94B}
{4E0E51AA-7B59-4808-B7CC-9EAEDF397460} = {915EF238-CA0D-4E73-AF50-88CB3F29C7AD}
EndGlobalSection
EndGlobal
@@ -8,7 +8,6 @@
public int Integer;
public double Real;
public string Identifier;
public DateTime DateTime;
public ImmutableList<IArithmeticExpression> Arguments;
public ImmutableList<Assignment> Assignments;
public IArithmeticExpression Argument;
@@ -1,4 +1,6 @@
using System;
using System.Runtime.Versioning;
using Microsoft.Win32;
namespace BCG
@@ -10,6 +12,7 @@ namespace BCG
rk.SetValue (name, value.ToString ());
}
[SupportedOSPlatform("windows")]
public static double getValue (RegistryKey rk, string name)
{
object v = rk.GetValue (name);
@@ -1,19 +1,11 @@
namespace Bts.BooleanExpressions;
using bts;
class BooleanExpression : IBooleanExpression {
readonly Func<IDictionary<string, object?>, bool> evaluate;
public BooleanExpression(Func<IDictionary<string, object?>, bool> evaluate) =>
this.evaluate = evaluate;
public bool Evaluate(IDictionary<string, object?> env) {
try {
return evaluate(env);
} catch (Exception ex) {
Log.assert(false, $"BooleanExpression.Evaluate failed: {ex}");
throw;
}
}
public bool Evaluate(IDictionary<string, object?> env) =>
evaluate(env);
}
@@ -41,17 +41,11 @@ Identifier {Letter}({Letter}*[0-9]*_*)*
OpenParenthesis \(
CloseParenthesis \)
Comma ,
Negate '!'
MapsTo ':'
IsOfType '/'
%%
{StringConstant} {
yylval.String = yytext.Substring(1, yytext.Length - 2).Replace(@"\'", "'");
return (int)Token.STRING_CONSTANT;
}
{BooleanConstant} {
yylval.Boolean = yytext.ToLower() switch {
"true" => true,
@@ -62,6 +56,11 @@ IsOfType '/'
return (int)Token.BOOLEAN_CONSTANT;
}
{StringConstant} {
yylval.String = yytext.Substring(1, yytext.Length - 2).Replace(@"\'", "'");
return (int)Token.STRING_CONSTANT;
}
{NullConstant} {
yylval.Null = DBNull.Value;
return (int)Token.NULL_CONSTANT;
@@ -94,10 +93,6 @@ IsOfType '/'
return (int)Token.COMMA;
}
{Negate} {
return (int)Token.NEGATE;
}
{MapsTo} {
return (int)Token.MAPS_TO;
}
@@ -0,0 +1 @@
VITE_DATASTREAM_WEBSOCKET_URL=wss://localhost:8443/ws
@@ -0,0 +1 @@
VITE_DATASTREAM_WEBSOCKET_URL=wss://localhost:8443/ws
@@ -0,0 +1,20 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
settings: { react: { version: '18.3.1' } },
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/0.5.128-alpha">
<PropertyGroup>
<StartupCommand>npm run dev</StartupCommand>
<JavaScriptTestRoot>src\</JavaScriptTestRoot>
<JavaScriptTestFramework>Jest</JavaScriptTestFramework>
<!-- Allows the build (or compile) script located on package.json to run on Build -->
<ShouldRunBuildScript>true</ShouldRunBuildScript>
<!-- Folder where production build objects will be placed -->
<PublishAssetsDirectory>$(MSBuildProjectDirectory)\distill</PublishAssetsDirectory>
</PropertyGroup>
</Project>
@@ -0,0 +1,8 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/ico" href="/logo.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BTS Cloud Streams</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<disabledPackageSources>
<clear />
</disabledPackageSources>
</configuration>
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,43 @@
{
"name": "bts.datastreamwebapp.client",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@angular/material": "^18.0.4",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@fontsource/noto-sans": "^5.0.22",
"@material/data-table": "^15.0.0-canary.7f224ddd4.0",
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
"@mui/x-data-grid": "^7.7.1",
"bulma": "^1.0.1",
"classnames": "^2.5.1",
"react": "^18.3.1",
"react-data-grid": "^7.0.0-beta.44",
"react-data-table-component": "^7.6.2",
"react-dom": "^18.3.1",
"react-json-view-lite": "^1.4.0",
"react-pro-sidebar": "^1.1.0",
"react-use-websocket": "^4.8.1",
"rpc-websockets": "^7.11.2"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.2",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"react-mui-sidebar": "^1.3.5",
"vite": "^5.2.13"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,40 @@
import { Sidebar, Menu, MenuItem, Submenu, Logo } from "react-mui-sidebar";
import './App.css';
const AnimatedSideBar = () => {
return (
<Sidebar className={"sidebar-container"} width={"270px"}>
<Logo img="https://adminmart.com/wp-content/uploads/2024/03/logo-admin-mart-news.png">
AdminMart
</Logo>
<Menu subHeading="HOME">
<MenuItem link="/" badge="true">
Modern
</MenuItem>
<MenuItem>eCommerce</MenuItem>
<MenuItem>Analytical</MenuItem>
</Menu>
<Menu subHeading="APPS">
<MenuItem>Chat</MenuItem>
<MenuItem>Calendar</MenuItem>
</Menu>
<Menu subHeading="OTHERS">
<Submenu title="Menu Level">
<MenuItem>Post</MenuItem>
<MenuItem>Details</MenuItem>
<Submenu title="Level 2">
<MenuItem>new</MenuItem>
<MenuItem>Hello</MenuItem>
</Submenu>
</Submenu>
<MenuItem>Chip</MenuItem>
<MenuItem target="_blank" link="google.com">
External Link
</MenuItem>
</Menu>
</Sidebar>
);
};
export default AnimatedSideBar;
@@ -0,0 +1,101 @@
@use "@material/checkbox";
@use "@material/icon-button";
@use "@material/data-table/data-table";
.root-div {
display: flex;
flex-direction: row;
align-items: flex-start;
}
.main-content-div {
display: flex;
flex-direction: row;
}
.title-div {
width: 100%;
}
.container {
display: flex;
}
.container-column {
display: flex;
flex-direction: column;
}
.container-row {
display: flex;
flex-direction: row;
align-items: stretch;
}
box.query-input-form {
/* p: 2, display: "flex", flexFlow: "column", alignItems: "stretch" */
display: flex;
flex-flow: column;
align-items: stretch;
}
/* li.ps-menuitem-root:hover {
background-color: #ff9922;
} */
.container-row-centered {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
h1#main-header {
padding: 4px 8px 4px 8px;
text-align: center;
}
td:hover {
background-color: cadetblue;
}
td#cell-decimal {
text-align: right;
}
td#cell-integer {
text-align: right;
}
td#cell-string {
text-align: center;
}
td#cell-date-time {
text-align: center;
}
/* Sidebar.css */
/* .sidebar-container {
display: flex;
flex-direction: column;
}
.sidebar {
position: relative;
top: 0;
left: 0;
height: 100vh;
background-color: #333;
color: white;
overflow: hidden;
transition: transform 0.3s ease;
}
.sidebar.closed {
width: 0;
}
.sidebar.open {
width: 250px;
} */
@@ -0,0 +1,173 @@
import { ThemeProvider, CssBaseline, GlobalStyles } from '@mui/material';
import QueryStatusView from './QueryStatusView';
import useMediaQuery from '@mui/material/useMediaQuery';
import QueryControl from './QueryControl';
import DataTable from './DataTable';
import { useState, useEffect, useMemo } from 'react';
import useWebSocket, { ReadyState } from 'react-use-websocket';
import Sidebar from './SideBar';
import getTheme from './Theme';
import '@fontsource/noto-sans';
import './App.css';
import './SideBar.css';
const webSocketUrl = import.meta.env.VITE_DATASTREAM_WEBSOCKET_URL;
// Always use the same subscription id (1), because we only show 1 table at a time.
// The server will automatically dispose the previous subscription and start a new one
// if we're currently subscribed to another query.
const subscriptionId = "1";
var rpcReqIdCounter = 1;
function subscribeReq(query) {
return {
jsonrpc: "2.0",
id: rpcReqIdCounter++,
method: "subscribe",
params: {
subscription_id: subscriptionId,
query: query
}
};
}
function unsubscribeReq() {
return {
jsonrpc: "2.0",
id: rpcReqIdCounter++,
method: "unsubscribe",
params: {
subscription_id: subscriptionId,
}
};
}
const App = () => {
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const theme = getTheme({ mode: prefersDarkMode ? 'dark' : 'light' });
const globalStyles = {
':root': {
'--primary-color': theme.palette.primary.main,
'--primary-dark-color': theme.palette.primary.dark,
'--secondary-color': theme.palette.secondary.main,
'--secondary-dark-color': theme.palette.secondary.dark,
'--background-color': theme.palette.background.default,
},
};
const [blueTable, setBlueTable] = useState({ columns: null, rows: null });
const [queryError, setQueryError] = useState(null);
const [queryState, setQueryState] = useState("Unsubscribed");
const queryStateDetails = useMemo(() => {
if (queryState == "Unsubscribed") {
return "Enter query above, then click the play button to subscribe";
}
if (queryState == "Subscribed") {
return null;
}
}, [queryState]);
const [sidebarIsOpen, setSidebarIsOpen] = useState(false);
const [cannedQuery, setCannedQuery] = useState(null);
const onCannedQueryClicked = (cannedQuery) => {
console.info(`Executing canned query: ${JSON.stringify(cannedQuery)}`);
const queryText = cannedQuery.query.join("\n");
setCannedQuery({ queryText: queryText, clickTime: Date.now() });
subscribe(queryText);
};
const { sendJsonMessage, getWebSocket, readyState } = useWebSocket(
webSocketUrl,
{
onOpen: (e) => console.info(`WebSocket open. ${JSON.stringify(e)}`),
onClose: (e) => console.info(`WebSocket closed. ${JSON.stringify(e)}`),
onError: (e) => console.error(`WebSocket error: ${JSON.stringify(e)}`),
shouldReconnect: () => true,
});
useEffect(() => {
console.info(`WebSocket readyState: ${readyState}`)
if (readyState != ReadyState.OPEN) {
return;
}
const ws = getWebSocket();
ws.onmessage = e => {
console.info(`Received message: ${e.data}`);
const msg = JSON.parse(e.data);
setQueryError(msg.error);
if (msg.error != null) {
setQueryState("Error");
} else if (msg.id != null) {
setQueryState(msg.result.status);
} else {
console.assert(msg.params.subscription_id == subscriptionId);
console.info(`Received rows: ${msg.params.table.all}`);
setBlueTable({ columns: msg.params.table.columns, rows: msg.params.table.all });
}
};
}, [getWebSocket, readyState]);
const subscribe = (query) => {
const req = subscribeReq(query);
console.info(`Subscribing: ${JSON.stringify(subscribeReq(query))}`);
sendJsonMessage(req);
};
const unsubscribe = () => {
const req = unsubscribeReq();
console.info(`Unsubscribing: ${JSON.stringify(req)}`);
sendJsonMessage(req);
};
const onOpened = () => {
setSidebarIsOpen(true);
};
const onClosed = () => {
setSidebarIsOpen(false);
};
const sidebarWidth = sidebarIsOpen ? '250px' : '65px';
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<GlobalStyles styles={globalStyles} />
<div className='root-div'>
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
flexWrap: 'nowrap',
width: sidebarWidth,
transition: 'width 0.3s ease',
}}>
<Sidebar onSelection={onCannedQueryClicked} onOpened={onOpened} onClosed={onClosed} />
</div>
<div style={{
display: 'flex',
flexDirection: 'column',
left: sidebarWidth,
width: '100%',
transition: 'width 0.3s ease',
}}>
<div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'flex-start', margin: theme.spacing(2, 0, 0, 0) }}>
<div style={{ width: '90%', rightMargin: '100px' }}>
<QueryControl
queryState={queryState}
queryText={cannedQuery?.queryText}
queryTextResetTime={cannedQuery?.clickTime}
subscribe={subscribe}
unsubscribe={unsubscribe} />
</div>
<div style={{ width: '90%', rightMargin: '100px' }}>
<QueryStatusView queryState={queryState} details={queryStateDetails} error={queryError} />
<DataTable columns={blueTable?.columns ?? []} rows={blueTable?.rows ?? []} />
</div>
</div>
</div>
</div>
</ThemeProvider>
);
};
export default App;
@@ -0,0 +1,53 @@
@use "@material/checkbox";
@use "@material/icon-button";
@use "@material/data-table/data-table";
.data-grid-container {
overflow: auto;
white-space: nowrap;
}
.data-grid {
max-height: 1000px;
}
div[role=columnheader] {
border-top: 0px;
border-bottom: 0px;
background-color: var(--primary-dark-color);
}
/* div[role=columnheader] {
border-top: 0px;
border-bottom: 0px;
background-color: var(--primary-dark-color);
}
.rdg-row {
border: 0px;
border-top: 0px;
border-bottom: 0px;
background-color: rgba(var(--primary-dark-color), 0.8);
color: var(--primary-color);
}
.rdg-row-odd {
border: 0px;
border-top: 0px;
border-bottom: 0px;
background-color: var(--primary-dark-color);
color: var(--primary-color);
}
.rdg-cell {
border-bottom: 0px;
}
.rdg-cell.even {
border-bottom: 5px;
}
.rdg-cell.odd {
border-bottom: 5px;
} */
@@ -0,0 +1,32 @@
@use "@material/checkbox";
@use "@material/icon-button";
@use "@material/data-table/data-table";
th {
font-size: large;
padding: 15px;
}
td {
padding: 15px;
}
td:hover {
background-color: cadetblue;
}
td#cell-decimal {
text-align: right;
}
td#cell-integer {
text-align: right;
}
td#cell-string {
text-align: center;
}
td#cell-date-time {
text-align: center;
}
@@ -0,0 +1,74 @@
import PropTypes from 'prop-types';
import { useTheme } from '@mui/material/styles';
import { DataGrid } from '@mui/x-data-grid';
import './DataGrid.css';
import { useMemo, useState } from 'react';
export default function DataTable({ columns, rows }) {
const theme = useTheme();
const [columnWidths, setColumnWidths] = useState({});
const handleColumnResize = (params) => {
const newWidth = params.width;
setColumnWidths((prevWidths) => ({
...prevWidths,
[params.colDef.field]: params.width,
}));
};
const getGridColDef = useMemo(() => {
return col => {
return {
field: col.name,
headerName: col.name,
width: columnWidths[col.name],
};
};
}, [columnWidths]);
return (
<DataGrid
className='data-grid-container'
autosizeOnMount={false}
columns={columns?.map(getGridColDef) ?? []}
onColumnResize={handleColumnResize}
rows={rows ?? []}
sx={{
margin: theme.spacing(2, 0, 0, 0),
borderColor: 'primary.main',
backgroundColor: 'primary.dark',
color: 'text.primary',
'& .MuiDataGrid-columnHeader': {
color: 'primary.main',
fontWeight: 700,
backgroundColor: 'primary.dark'
},
'& .MuiDataGrid-filler': {
color: 'primary.main',
fontWeight: 700,
backgroundColor: 'primary.dark'
},
'& .MuiDataGrid-cell': {
color: 'text.primary',
},
'& .MuiDataGrid-cell:hover': {
color: 'primary.main',
},
'& .MuiDataGrid-row': {
'--DataGrid-rowBorderColor': 'secondary.dark'
}
}}
/>
);
}
DataTable.propTypes = {
columns: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
})
).isRequired,
rows: PropTypes.arrayOf(PropTypes.array).isRequired,
apiRef: PropTypes.object,
};
@@ -0,0 +1,119 @@
import PropTypes from 'prop-types';
import DataTable from './DataTable';
import { useRef, useState, useEffect } from 'react';
import { useGridApiRef } from '@mui/x-data-grid';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Paper from '@mui/material/Paper';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import StopIcon from '@mui/icons-material/Stop';
import TextField from '@mui/material/TextField';
import Tooltip from '@mui/material/Tooltip';
const QueryControl = (props) => {
const { queryState, queryText, queryTextResetTime, subscribe, unsubscribe } = props;
// const theme = useTheme();
let textInput = useRef();
const [queryTextChangeTime, setQueryTextChangeTime] = useState(null);
useEffect(() => {
if (queryText == null || textInput.current == null) {
return;
}
// Init text field (unless a non-empty string is already present, or the user has modified
// the query - we don't want to overwrite their changes).
if (queryTextChangeTime == null || queryTextResetTime > queryTextChangeTime) {
console.info(`Initializing query text: ${queryText}`)
textInput.current.value = queryText;
}
}, [queryText, queryTextChangeTime, queryTextResetTime]);
const onClearClick = () => {
textInput.current.value = '';
};
const onRunClick = () => {
subscribe(textInput.current.value);
};
const onStopClick = () => {
unsubscribe();
};
const onKeyDown = (e) => {
// Detect keyboard shortcuts.
// Ctrl+Enter : Run
if (e.ctrlKey && e.key === 'Enter') {
onRunClick();
}
};
const onChange = (e) => {
// So we know not to overwrite the changes with the canned query on re-render
setQueryTextChangeTime(Date.now());
};
return (
<Box sx={{ display: "flex", flexFlow: "column" }}>
<Paper elevation={3} sx={{ color: 'primary.main' }}>
<Box sx={{ p: 2 }}>
<TextField
fullWidth
multiline
rows={10}
variant="outlined"
onChange={onChange}
onKeyDown={onKeyDown}
inputRef={textInput}
placeholder="Enter Query..."
/>
</Box>
<Box sx={{ p: 2, borderTop: 1 }}>
<Grid container spacing={2} justifyContent="flex-start">
<Grid item xs>
<Tooltip title="Clear Query Text">
<Button onClick={onClearClick} sx={{ padding: '11px', color: 'text.dark' }}>
Clear
</Button>
</Tooltip>
</Grid>
<Grid item>
<Tooltip title="Run (Ctrl+Enter)">
<Button onClick={onRunClick} sx={{
color: `queryStartButton.${queryState}.color`,
bgcolor: `queryStartButton.${queryState}.backgroundColor`,
padding: '11px'
}}>
<PlayArrowIcon />
</Button>
</Tooltip>
</Grid>
<Grid item>
<Tooltip title="Stop">
<Button onClick={onStopClick} sx={{
color: `queryStopButton.${queryState}.color`,
bgcolor: `queryStopButton.${queryState}.backgroundColor`,
padding: '11px'
}}>
<StopIcon />
</Button>
</Tooltip>
</Grid>
</Grid>
</Box>
</Paper>
</Box>
);
};
QueryControl.propTypes = {
columns: PropTypes.array,
rows: PropTypes.array,
isRunning: PropTypes.bool,
queryText: PropTypes.string,
queryTextResetTime: PropTypes.number,
queryState: PropTypes.string,
subscribe: PropTypes.func,
unsubscribe: PropTypes.func,
};
export default QueryControl;
@@ -0,0 +1,73 @@
import { useState } from 'react';
import PropTypes from 'prop-types';
import { Box, Typography, Collapse } from '@mui/material';
import Alert from '@mui/material/Alert';
import AlertTitle from '@mui/material/AlertTitle';
import { useTheme } from '@mui/material/styles';
import { alpha } from '@mui/system';
const QueryStatusView = ({ queryState, details, error }) => {
const theme = useTheme();
const [open, setOpen] = useState(false);
let header = queryState;
let headerContent = null;
let detailElements = [];
let severity = queryState == "Subscribed" ? "success" : "info";
let cursor = details == null ? "default" : "pointer";
if (error != null) {
severity = "error";
cursor = "pointer";
header = `Error Code: ${error.code}`;
headerContent = error.message;
detailElements.push(<Typography variant="body1">{error.data.error_message}</Typography>);
detailElements.push(<Typography variant="subtitle1">Stack Trace:</Typography>);
(error.data.stack_trace ?? []).forEach(line => detailElements.push(
<Typography variant="body2" component="pre" sx={{ whiteSpace: 'pre-wrap' }}>
{line}
</Typography>));
} else if (details != null) {
detailElements.push(<Typography variant="body1">{details}</Typography>);
}
return (
<Box sx={{ margin: theme.spacing(2, 0, 0, 0) }}>
<Alert
severity={severity}
onClick={() => setOpen(!open)}
sx={{
margin: theme.spacing(2, 0, 0, 0),
cursor: cursor,
}}>
<AlertTitle>{header}</AlertTitle>
{headerContent}
</Alert>
{detailElements.length == 0
? null
: <Collapse in={open}>
<Box
sx={{
backgroundColor: alpha(theme.palette[severity].light, 0.75),
color: theme.palette[severity].contrastText,
padding: theme.spacing(2),
marginTop: theme.spacing(0),
borderRadius: theme.shape.borderRadius,
}}
>
{detailElements}
</Box>
</Collapse>}
</Box>
);
};
QueryStatusView.propTypes = {
queryState: PropTypes.string,
details: PropTypes.string,
error: PropTypes.shape({
code: PropTypes.number,
message: PropTypes.string,
data: PropTypes.object,
})
};
export default QueryStatusView;
@@ -0,0 +1,27 @@
import 'react-data-grid/lib/styles.css';
import PropTypes from 'prop-types';
import DataGrid from 'react-data-grid';
import './DataGrid.css';
function getGridColDef(col) {
return {
key: col.name,
name: col.name,
};
}
export default function ReactDataGrid({columns, rows}) {
return (
<DataGrid className={"data-grid"} rows={rows} columns={columns.map(getGridColDef)} />
);
}
ReactDataGrid.propTypes = {
columns: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
})
).isRequired,
rows: PropTypes.arrayOf(PropTypes.array).isRequired,
};
@@ -0,0 +1,98 @@
/* Sidebar.css */
.app-container {
display: flex;
width: 100%;
overflow: hidden;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
}
.sidebar {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
background-color: transparent;
color: white;
overflow: hidden;
transition: width 0.3s ease;
}
.sidebar.open {
width: 100%;
}
.sidebar.closed {
width: 0%;
}
.sidebar-toggle-button {
border: 0px;
border-radius: 50%;
box-shadow: rgba(9, 30, 66, 0.08) 0px 0px 0px 1px, rgba(9, 30, 66, 0.08) 0px 2px 4px 1px;
color: var(--primary-color);
cursor: pointer;
height: 24px;
opacity: 1;
outline: 0px;
padding: 0px;
background-color: transparent;
transition: background-color 100ms linear 0s, color 100ms linear 0s, opacity 300ms cubic-bezier(0.2, 0, 0, 1) 0s, transform 300ms cubic-bezier(0.2, 0, 0, 1) 0s;
width: 20%;
}
.sidebar-toggle-button:hover {
transition: background-color 100ms linear 0s, color 100ms linear 0s, opacity 300ms cubic-bezier(0.2, 0, 0, 1) 0s, transform 300ms cubic-bezier(0.2, 0, 0, 1) 0s;
}
.main-content.open {
width: 165px;
transition: width 0.3s ease;
background-color: transparent;
}
.main-content.closed {
width: 0%;
/* min-width: 25px; */
transition: width 0.3s ease;
background-color: transparent;
}
.sidebar-menu-item {
width: 100%;
box-sizing: border-box;
}
.sidebar-menu-item.open {
width: 100%;
/* justify-content: flex-start; */
transition: width 0.3s ease;
}
.sidebar-menu-item.closed {
width: 0%;
min-width: 0px;
/* justify-content: flex-start; */
transition: width 0.3s ease;
}
.sidebar-menu-item-content.open {
width: 100%;
/* justify-content: flex-start; */
transition: width 0.3s ease;
}
.sidebar-menu-item-content.closed {
width: 0px;
/* justify-content: flex-start; */
transition: width 0.3s ease;
}
/* Ensure span can shrink */
.button-text {
display: block; /* Make span a block element */
width: 100%; /* Ensure it takes the full width of its parent */
overflow: hidden; /* Hide any overflow content */
white-space: nowrap; /* Prevent text wrapping */
}
@@ -0,0 +1,109 @@
import { useState, useEffect } from 'react';
import Button from '@mui/material/Button';
import { useTheme } from '@mui/material/styles';
import PropTypes from 'prop-types';
import Tooltip from '@mui/material/Tooltip';
import ChevronLeftRounded from "@mui/icons-material/ChevronLeftRounded";
import ChevronRightRounded from '@mui/icons-material/ChevronRightRounded';
import FileCopyIcon from '@mui/icons-material/FileCopy';
import './SideBar.css';
const Sidebar = ({ onSelection, onOpened, onClosed }) => {
const theme = useTheme();
const [isOpen, setIsOpen] = useState(false);
const [cannedQueries, setCannedQueries] = useState([]);
const Icon = isOpen ? ChevronLeftRounded : ChevronRightRounded;
const toggleIsOpen = () => {
if (isOpen) {
setIsOpen(false);
if (onClosed != null) {
onClosed();
}
} else {
setIsOpen(true);
if (onOpened != null) {
onOpened();
}
}
};
const onMenuItemClick = (event, cannedQuery) => {
console.info(`menu item clicked: ${cannedQuery.name}`);
onSelection(cannedQuery);
};
// const onMenuItemClick = async (event, cannedQuery) => {
// try {
// await navigator.clipboard.writeText(cannedQuery.query.join("\n"));
// alert(`Copied ${cannedQuery.name} to clipboard`);
// } catch (err) {
// console.error('Failed to copy query text: ', err);
// }
// };
useEffect(() => {
const fetchCannedQueries = async () => {
try {
const response = await fetch('api/cannedqueries');
const data = await response.json();
setCannedQueries(data);
} catch (error) {
console.error('Error fetching WebSocket config:', error);
}
};
fetchCannedQueries();
}, []);
const mainContentClass = `main-content ${isOpen ? 'open' : 'closed'}`;
const menuItemClass = `sidebar-menu-item ${isOpen ? 'open' : 'closed'}`;
const menuItemContentClass = `sidebar-menu-item-content ${isOpen ? 'open' : 'closed'}`;
const toggleButtonTitle = `${isOpen ? 'Hide' : 'Show'} Common Queries`;
return (
<div
className={"sidebar"}
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'flexStart',
alignItems: 'flexStart',
margin: theme.spacing(2, 0, 0, 0)
}}
>
<div className={mainContentClass}>
{cannedQueries.map(cq =>
<Button key={cq.name} className={menuItemClass} onClick={(event) => onMenuItemClick(event, cq)}>
<div className={menuItemContentClass}>
<span className='button-text'>{cq.name}</span>
</div>
</Button>)
}
{/* {cannedQueries.map(cq =>
<Button
variant="contained"
color="primary"
startIcon={<FileCopyIcon />}
onClick={onMenuItemClick}
>
<div className={menuItemContentClass}>
<span className='button-text'>{cq.name}</span>
</div>
</Button>)} */}
</div>
<Tooltip title={toggleButtonTitle}>
<Button onClick={toggleIsOpen}>
<Icon className="w-5 h-5" />
</Button>
</Tooltip>
</div>
);
};
export default Sidebar;
Sidebar.propTypes = {
onSelection: PropTypes.func,
onOpened: PropTypes.func,
onClosed: PropTypes.func,
};
@@ -0,0 +1,112 @@
import { createTheme } from '@mui/material/styles';
import PropTypes from 'prop-types';
import '@fontsource/noto-sans';
import './App.css';
const getLightPalette = () => {
return {
mode: 'light',
primary: {
light: '#6573c3',
main: '#3f51b5',
dark: '#2c387e',
contrastText: '#ffffff',
},
secondary: {
light: '#f73378',
main: '#f50057',
dark: '#ab003c',
contrastText: '#ffffff',
},
text: {
primary: '#000000c4',
secondary: '#00000086',
disabled: '#0000005b',
hint: '#0000005b',
}
};
};
const getDarkPalette = () => {
return {
mode: 'dark',
primary: {
light: '#bcc5ff',
main: '#33baf9',
dark: '#202334',
contrastText: '#78a2ac',
},
secondary: {
light: '#9fd0b8',
main: '#5eb289',
dark: '#ffffff12',
contrastText: '#c8d4f7',
},
action: {
hoverOpacity: 0.08
},
text: {
primary: '#eaf8f8',
dark: '#eaf8f8a0'
},
queryStartButton: {
Subscribed: {
color: '#33baf9',
backgroundColor: '#ffffff40',
},
Unsubscribed: {
color: '#ffffff40',
backgroundColor: '#ffffff00',
}
},
queryStopButton: {
Subscribed: {
color: '#ff4444a0',
backgroundColor: '#ffffff40',
},
Unsubscribed: {
color: '#ff444450',
backgroundColor: '#ffffff00',
}
},
error: {
light: '#e57373',
main: '#f44336',
dark: '#d32f2f',
contrastText: '#fff',
},
warning: {
light: '#ffb74d',
main: '#ff9800',
dark: '#f57c00',
contrastText: '#000',
},
info: {
light: '#64b5f6',
main: '#2196f3',
dark: '#303030',
contrastText: '#fff',
},
success: {
light: '#81c784',
main: '#4caf50',
dark: '#388e3c',
contrastText: '#000',
},
};
};
const getTheme = ({ mode }) => {
return createTheme({
typography: {
fontFamily: '"Helvetica"'
},
palette: mode == 'dark' ? getDarkPalette() : getLightPalette()
});
};
export default getTheme;
getTheme.propTypes = {
mode: PropTypes.string.isRequired,
};
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1,51 @@
.dark-high-contrast {
--md-sys-color-primary: rgb(251 250 255);
--md-sys-color-surface-tint: rgb(165 200 255);
--md-sys-color-on-primary: rgb(0 0 0);
--md-sys-color-primary-container: rgb(173 204 255);
--md-sys-color-on-primary-container: rgb(0 0 0);
--md-sys-color-secondary: rgb(250 250 255);
--md-sys-color-on-secondary: rgb(0 0 0);
--md-sys-color-secondary-container: rgb(159 208 255);
--md-sys-color-on-secondary-container: rgb(0 0 0);
--md-sys-color-tertiary: rgb(255 249 251);
--md-sys-color-on-tertiary: rgb(0 0 0);
--md-sys-color-tertiary-container: rgb(222 193 231);
--md-sys-color-on-tertiary-container: rgb(0 0 0);
--md-sys-color-error: rgb(255 249 249);
--md-sys-color-on-error: rgb(0 0 0);
--md-sys-color-error-container: rgb(255 186 177);
--md-sys-color-on-error-container: rgb(0 0 0);
--md-sys-color-background: rgb(17 19 24);
--md-sys-color-on-background: rgb(225 226 233);
--md-sys-color-surface: rgb(17 19 24);
--md-sys-color-on-surface: rgb(255 255 255);
--md-sys-color-surface-variant: rgb(67 71 78);
--md-sys-color-on-surface-variant: rgb(251 250 255);
--md-sys-color-outline: rgb(200 202 212);
--md-sys-color-outline-variant: rgb(200 202 212);
--md-sys-color-shadow: rgb(0 0 0);
--md-sys-color-scrim: rgb(0 0 0);
--md-sys-color-inverse-surface: rgb(225 226 233);
--md-sys-color-inverse-on-surface: rgb(0 0 0);
--md-sys-color-inverse-primary: rgb(0 42 83);
--md-sys-color-primary-fixed: rgb(219 231 255);
--md-sys-color-on-primary-fixed: rgb(0 0 0);
--md-sys-color-primary-fixed-dim: rgb(173 204 255);
--md-sys-color-on-primary-fixed-variant: rgb(0 22 49);
--md-sys-color-secondary-fixed: rgb(214 233 255);
--md-sys-color-on-secondary-fixed: rgb(0 0 0);
--md-sys-color-secondary-fixed-dim: rgb(159 208 255);
--md-sys-color-on-secondary-fixed-variant: rgb(0 24 42);
--md-sys-color-tertiary-fixed: rgb(249 222 255);
--md-sys-color-on-tertiary-fixed: rgb(0 0 0);
--md-sys-color-tertiary-fixed-dim: rgb(222 193 231);
--md-sys-color-on-tertiary-fixed-variant: rgb(33 14 42);
--md-sys-color-surface-dim: rgb(17 19 24);
--md-sys-color-surface-bright: rgb(55 57 62);
--md-sys-color-surface-container-lowest: rgb(12 14 19);
--md-sys-color-surface-container-low: rgb(25 28 32);
--md-sys-color-surface-container: rgb(29 32 36);
--md-sys-color-surface-container-high: rgb(40 42 47);
--md-sys-color-surface-container-highest: rgb(50 53 58);
}
@@ -0,0 +1,51 @@
.dark-medium-contrast {
--md-sys-color-primary: rgb(173 204 255);
--md-sys-color-surface-tint: rgb(165 200 255);
--md-sys-color-on-primary: rgb(0 22 49);
--md-sys-color-primary-container: rgb(112 146 198);
--md-sys-color-on-primary-container: rgb(0 0 0);
--md-sys-color-secondary: rgb(159 208 255);
--md-sys-color-on-secondary: rgb(0 24 42);
--md-sys-color-secondary-container: rgb(100 149 193);
--md-sys-color-on-secondary-container: rgb(0 0 0);
--md-sys-color-tertiary: rgb(222 193 231);
--md-sys-color-on-tertiary: rgb(33 14 42);
--md-sys-color-tertiary-container: rgb(162 136 171);
--md-sys-color-on-tertiary-container: rgb(0 0 0);
--md-sys-color-error: rgb(255 186 177);
--md-sys-color-on-error: rgb(55 0 1);
--md-sys-color-error-container: rgb(255 84 73);
--md-sys-color-on-error-container: rgb(0 0 0);
--md-sys-color-background: rgb(17 19 24);
--md-sys-color-on-background: rgb(225 226 233);
--md-sys-color-surface: rgb(17 19 24);
--md-sys-color-on-surface: rgb(251 250 255);
--md-sys-color-surface-variant: rgb(67 71 78);
--md-sys-color-on-surface-variant: rgb(200 202 212);
--md-sys-color-outline: rgb(160 163 171);
--md-sys-color-outline-variant: rgb(128 131 139);
--md-sys-color-shadow: rgb(0 0 0);
--md-sys-color-scrim: rgb(0 0 0);
--md-sys-color-inverse-surface: rgb(225 226 233);
--md-sys-color-inverse-on-surface: rgb(40 42 47);
--md-sys-color-inverse-primary: rgb(35 73 120);
--md-sys-color-primary-fixed: rgb(212 227 255);
--md-sys-color-on-primary-fixed: rgb(0 17 40);
--md-sys-color-primary-fixed-dim: rgb(165 200 255);
--md-sys-color-on-primary-fixed-variant: rgb(10 55 100);
--md-sys-color-secondary-fixed: rgb(206 229 255);
--md-sys-color-on-secondary-fixed: rgb(0 18 34);
--md-sys-color-secondary-fixed-dim: rgb(154 203 250);
--md-sys-color-on-secondary-fixed-variant: rgb(0 57 91);
--md-sys-color-tertiary-fixed: rgb(247 216 255);
--md-sys-color-on-tertiary-fixed: rgb(28 9 37);
--md-sys-color-tertiary-fixed-dim: rgb(218 189 226);
--md-sys-color-on-tertiary-fixed-variant: rgb(67 46 76);
--md-sys-color-surface-dim: rgb(17 19 24);
--md-sys-color-surface-bright: rgb(55 57 62);
--md-sys-color-surface-container-lowest: rgb(12 14 19);
--md-sys-color-surface-container-low: rgb(25 28 32);
--md-sys-color-surface-container: rgb(29 32 36);
--md-sys-color-surface-container-high: rgb(40 42 47);
--md-sys-color-surface-container-highest: rgb(50 53 58);
}
@@ -0,0 +1,51 @@
.dark {
--md-sys-color-primary: rgb(165 200 255);
--md-sys-color-surface-tint: rgb(165 200 255);
--md-sys-color-on-primary: rgb(0 49 94);
--md-sys-color-primary-container: rgb(34 72 118);
--md-sys-color-on-primary-container: rgb(212 227 255);
--md-sys-color-secondary: rgb(154 203 250);
--md-sys-color-on-secondary: rgb(0 51 83);
--md-sys-color-secondary-container: rgb(12 74 114);
--md-sys-color-on-secondary-container: rgb(206 229 255);
--md-sys-color-tertiary: rgb(218 189 226);
--md-sys-color-on-tertiary: rgb(61 41 70);
--md-sys-color-tertiary-container: rgb(85 63 93);
--md-sys-color-on-tertiary-container: rgb(247 216 255);
--md-sys-color-error: rgb(255 180 171);
--md-sys-color-on-error: rgb(105 0 5);
--md-sys-color-error-container: rgb(147 0 10);
--md-sys-color-on-error-container: rgb(255 218 214);
--md-sys-color-background: rgb(17 19 24);
--md-sys-color-on-background: rgb(225 226 233);
--md-sys-color-surface: rgb(17 19 24);
--md-sys-color-on-surface: rgb(225 226 233);
--md-sys-color-surface-variant: rgb(67 71 78);
--md-sys-color-on-surface-variant: rgb(195 198 207);
--md-sys-color-outline: rgb(141 145 153);
--md-sys-color-outline-variant: rgb(67 71 78);
--md-sys-color-shadow: rgb(0 0 0);
--md-sys-color-scrim: rgb(0 0 0);
--md-sys-color-inverse-surface: rgb(225 226 233);
--md-sys-color-inverse-on-surface: rgb(46 48 53);
--md-sys-color-inverse-primary: rgb(60 96 144);
--md-sys-color-primary-fixed: rgb(212 227 255);
--md-sys-color-on-primary-fixed: rgb(0 28 58);
--md-sys-color-primary-fixed-dim: rgb(165 200 255);
--md-sys-color-on-primary-fixed-variant: rgb(34 72 118);
--md-sys-color-secondary-fixed: rgb(206 229 255);
--md-sys-color-on-secondary-fixed: rgb(0 29 50);
--md-sys-color-secondary-fixed-dim: rgb(154 203 250);
--md-sys-color-on-secondary-fixed-variant: rgb(12 74 114);
--md-sys-color-tertiary-fixed: rgb(247 216 255);
--md-sys-color-on-tertiary-fixed: rgb(39 20 48);
--md-sys-color-tertiary-fixed-dim: rgb(218 189 226);
--md-sys-color-on-tertiary-fixed-variant: rgb(85 63 93);
--md-sys-color-surface-dim: rgb(17 19 24);
--md-sys-color-surface-bright: rgb(55 57 62);
--md-sys-color-surface-container-lowest: rgb(12 14 19);
--md-sys-color-surface-container-low: rgb(25 28 32);
--md-sys-color-surface-container: rgb(29 32 36);
--md-sys-color-surface-container-high: rgb(40 42 47);
--md-sys-color-surface-container-highest: rgb(50 53 58);
}
@@ -0,0 +1,51 @@
.light-high-contrast {
--md-sys-color-primary: rgb(0 34 70);
--md-sys-color-surface-tint: rgb(60 96 144);
--md-sys-color-on-primary: rgb(255 255 255);
--md-sys-color-primary-container: rgb(29 68 114);
--md-sys-color-on-primary-container: rgb(255 255 255);
--md-sys-color-secondary: rgb(0 36 61);
--md-sys-color-on-secondary: rgb(255 255 255);
--md-sys-color-secondary-container: rgb(3 70 110);
--md-sys-color-on-secondary-container: rgb(255 255 255);
--md-sys-color-tertiary: rgb(46 26 55);
--md-sys-color-on-tertiary: rgb(255 255 255);
--md-sys-color-tertiary-container: rgb(81 59 89);
--md-sys-color-on-tertiary-container: rgb(255 255 255);
--md-sys-color-error: rgb(78 0 2);
--md-sys-color-on-error: rgb(255 255 255);
--md-sys-color-error-container: rgb(140 0 9);
--md-sys-color-on-error-container: rgb(255 255 255);
--md-sys-color-background: rgb(249 249 255);
--md-sys-color-on-background: rgb(25 28 32);
--md-sys-color-surface: rgb(249 249 255);
--md-sys-color-on-surface: rgb(0 0 0);
--md-sys-color-surface-variant: rgb(224 226 236);
--md-sys-color-on-surface-variant: rgb(32 36 43);
--md-sys-color-outline: rgb(63 67 74);
--md-sys-color-outline-variant: rgb(63 67 74);
--md-sys-color-shadow: rgb(0 0 0);
--md-sys-color-scrim: rgb(0 0 0);
--md-sys-color-inverse-surface: rgb(46 48 53);
--md-sys-color-inverse-on-surface: rgb(255 255 255);
--md-sys-color-inverse-primary: rgb(228 236 255);
--md-sys-color-primary-fixed: rgb(29 68 114);
--md-sys-color-on-primary-fixed: rgb(255 255 255);
--md-sys-color-primary-fixed-dim: rgb(0 45 88);
--md-sys-color-on-primary-fixed-variant: rgb(255 255 255);
--md-sys-color-secondary-fixed: rgb(3 70 110);
--md-sys-color-on-secondary-fixed: rgb(255 255 255);
--md-sys-color-secondary-fixed-dim: rgb(0 47 77);
--md-sys-color-on-secondary-fixed-variant: rgb(255 255 255);
--md-sys-color-tertiary-fixed: rgb(81 59 89);
--md-sys-color-on-tertiary-fixed: rgb(255 255 255);
--md-sys-color-tertiary-fixed-dim: rgb(57 37 66);
--md-sys-color-on-tertiary-fixed-variant: rgb(255 255 255);
--md-sys-color-surface-dim: rgb(217 218 224);
--md-sys-color-surface-bright: rgb(249 249 255);
--md-sys-color-surface-container-lowest: rgb(255 255 255);
--md-sys-color-surface-container-low: rgb(243 243 250);
--md-sys-color-surface-container: rgb(237 237 244);
--md-sys-color-surface-container-high: rgb(231 232 238);
--md-sys-color-surface-container-highest: rgb(225 226 233);
}
@@ -0,0 +1,51 @@
.light-medium-contrast {
--md-sys-color-primary: rgb(29 68 114);
--md-sys-color-surface-tint: rgb(60 96 144);
--md-sys-color-on-primary: rgb(255 255 255);
--md-sys-color-primary-container: rgb(83 118 167);
--md-sys-color-on-primary-container: rgb(255 255 255);
--md-sys-color-secondary: rgb(3 70 110);
--md-sys-color-on-secondary: rgb(255 255 255);
--md-sys-color-secondary-container: rgb(71 121 163);
--md-sys-color-on-secondary-container: rgb(255 255 255);
--md-sys-color-tertiary: rgb(81 59 89);
--md-sys-color-on-tertiary: rgb(255 255 255);
--md-sys-color-tertiary-container: rgb(133 108 141);
--md-sys-color-on-tertiary-container: rgb(255 255 255);
--md-sys-color-error: rgb(140 0 9);
--md-sys-color-on-error: rgb(255 255 255);
--md-sys-color-error-container: rgb(218 52 46);
--md-sys-color-on-error-container: rgb(255 255 255);
--md-sys-color-background: rgb(249 249 255);
--md-sys-color-on-background: rgb(25 28 32);
--md-sys-color-surface: rgb(249 249 255);
--md-sys-color-on-surface: rgb(25 28 32);
--md-sys-color-surface-variant: rgb(224 226 236);
--md-sys-color-on-surface-variant: rgb(63 67 74);
--md-sys-color-outline: rgb(91 95 103);
--md-sys-color-outline-variant: rgb(119 123 131);
--md-sys-color-shadow: rgb(0 0 0);
--md-sys-color-scrim: rgb(0 0 0);
--md-sys-color-inverse-surface: rgb(46 48 53);
--md-sys-color-inverse-on-surface: rgb(240 240 247);
--md-sys-color-inverse-primary: rgb(165 200 255);
--md-sys-color-primary-fixed: rgb(83 118 167);
--md-sys-color-on-primary-fixed: rgb(255 255 255);
--md-sys-color-primary-fixed-dim: rgb(58 93 141);
--md-sys-color-on-primary-fixed-variant: rgb(255 255 255);
--md-sys-color-secondary-fixed: rgb(71 121 163);
--md-sys-color-on-secondary-fixed: rgb(255 255 255);
--md-sys-color-secondary-fixed-dim: rgb(43 96 137);
--md-sys-color-on-secondary-fixed-variant: rgb(255 255 255);
--md-sys-color-tertiary-fixed: rgb(133 108 141);
--md-sys-color-on-tertiary-fixed: rgb(255 255 255);
--md-sys-color-tertiary-fixed-dim: rgb(107 84 116);
--md-sys-color-on-tertiary-fixed-variant: rgb(255 255 255);
--md-sys-color-surface-dim: rgb(217 218 224);
--md-sys-color-surface-bright: rgb(249 249 255);
--md-sys-color-surface-container-lowest: rgb(255 255 255);
--md-sys-color-surface-container-low: rgb(243 243 250);
--md-sys-color-surface-container: rgb(237 237 244);
--md-sys-color-surface-container-high: rgb(231 232 238);
--md-sys-color-surface-container-highest: rgb(225 226 233);
}
@@ -0,0 +1,51 @@
.light {
--md-sys-color-primary: rgb(60 96 144);
--md-sys-color-surface-tint: rgb(60 96 144);
--md-sys-color-on-primary: rgb(255 255 255);
--md-sys-color-primary-container: rgb(212 227 255);
--md-sys-color-on-primary-container: rgb(0 28 58);
--md-sys-color-secondary: rgb(46 98 140);
--md-sys-color-on-secondary: rgb(255 255 255);
--md-sys-color-secondary-container: rgb(206 229 255);
--md-sys-color-on-secondary-container: rgb(0 29 50);
--md-sys-color-tertiary: rgb(110 86 118);
--md-sys-color-on-tertiary: rgb(255 255 255);
--md-sys-color-tertiary-container: rgb(247 216 255);
--md-sys-color-on-tertiary-container: rgb(39 20 48);
--md-sys-color-error: rgb(186 26 26);
--md-sys-color-on-error: rgb(255 255 255);
--md-sys-color-error-container: rgb(255 218 214);
--md-sys-color-on-error-container: rgb(65 0 2);
--md-sys-color-background: rgb(249 249 255);
--md-sys-color-on-background: rgb(25 28 32);
--md-sys-color-surface: rgb(249 249 255);
--md-sys-color-on-surface: rgb(25 28 32);
--md-sys-color-surface-variant: rgb(224 226 236);
--md-sys-color-on-surface-variant: rgb(67 71 78);
--md-sys-color-outline: rgb(116 119 127);
--md-sys-color-outline-variant: rgb(195 198 207);
--md-sys-color-shadow: rgb(0 0 0);
--md-sys-color-scrim: rgb(0 0 0);
--md-sys-color-inverse-surface: rgb(46 48 53);
--md-sys-color-inverse-on-surface: rgb(240 240 247);
--md-sys-color-inverse-primary: rgb(165 200 255);
--md-sys-color-primary-fixed: rgb(212 227 255);
--md-sys-color-on-primary-fixed: rgb(0 28 58);
--md-sys-color-primary-fixed-dim: rgb(165 200 255);
--md-sys-color-on-primary-fixed-variant: rgb(34 72 118);
--md-sys-color-secondary-fixed: rgb(206 229 255);
--md-sys-color-on-secondary-fixed: rgb(0 29 50);
--md-sys-color-secondary-fixed-dim: rgb(154 203 250);
--md-sys-color-on-secondary-fixed-variant: rgb(12 74 114);
--md-sys-color-tertiary-fixed: rgb(247 216 255);
--md-sys-color-on-tertiary-fixed: rgb(39 20 48);
--md-sys-color-tertiary-fixed-dim: rgb(218 189 226);
--md-sys-color-on-tertiary-fixed-variant: rgb(85 63 93);
--md-sys-color-surface-dim: rgb(217 218 224);
--md-sys-color-surface-bright: rgb(249 249 255);
--md-sys-color-surface-container-lowest: rgb(255 255 255);
--md-sys-color-surface-container-low: rgb(243 243 250);
--md-sys-color-surface-container: rgb(237 237 244);
--md-sys-color-surface-container-high: rgb(231 232 238);
--md-sys-color-surface-container-highest: rgb(225 226 233);
}
@@ -0,0 +1,107 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
button#query-button {
height: 5em;
vertical-align: top;
}
button#play-button {
padding: '10px 20px';
font-size: '16px';
cursor: 'pointer';
background-color: '#28a745';
color: '#fff';
border: 'none';
border-radius: '5px';
};
button#pause-button {
background-color: #ffc107;
}
button#stop-button {
background-color: #dc3545;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
p#footer {
float: none;
clear: both;
vertical-align: bottom;
}
table#query-output {
float: none;
clear: both;
}
textarea#query-input {
height: 5em;
width: 50em;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
@@ -0,0 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
)
@@ -0,0 +1,80 @@
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vite';
import plugin from '@vitejs/plugin-react';
import fs from 'fs';
import path from 'path';
import child_process from 'child_process';
import process, { env } from 'process';
const baseFolder =
process.env.APPDATA !== undefined && process.env.APPDATA !== ''
? `${process.env.APPDATA}/ASP.NET/https`
: `${process.env.HOME}/.aspnet/https`;
const certificateName = "bts.datastreamwebapp.client";
const certFilePath = path.join(baseFolder, `${certificateName}.pem`);
const keyFilePath = path.join(baseFolder, `${certificateName}.key`);
if (!fs.existsSync(certFilePath) || !fs.existsSync(keyFilePath)) {
if (0 !== child_process.spawnSync('dotnet', [
'dev-certs',
'https',
'--export-path',
certFilePath,
'--format',
'Pem',
'--no-password',
], { stdio: 'inherit', }).status) {
throw new Error("Could not create certificate.");
}
}
const target = env.ASPNETCORE_HTTPS_PORT
? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}`
: env.ASPNETCORE_URLS
? env.ASPNETCORE_URLS.split(';')[0]
: 'https://localhost:8443';
export default defineConfig(({ $$command, mode, $$isSsrBuild, $$isPreview }) => {
return {
plugins: [plugin()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
build: {
minify: false,
sourcemap: true,
outDir: `../../../build/Bts.DataStreamWebApp.Client/${mode}/dist`
},
server: {
proxy: {
'^/api': {
target,
secure: false,
configure: (proxy) => {
proxy.on('error', (err) => {
console.log('proxy error', err);
});
proxy.on('proxyReq', (proxyReq, req) => {
console.log(`Forwarding ${proxyReq.method} ${proxyReq.protocol}//${proxyReq.host}${proxyReq.path} to ${req.socket.remoteAddress}${req.url}`);
});
proxy.on('proxyRes', (proxyRes, req, res) => {
console.log(`Forwarding response ${req.socket.localAddress} to ${res.socket.remoteAddress}. Status=${proxyRes.statusCode}`);
});
},
},
'^/ws': {
target,
secure: false
}
},
port: 5173,
https: {
key: fs.readFileSync(keyFilePath),
cert: fs.readFileSync(certFilePath),
}
}
};
});
@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
<SpaRoot>..\Bts.DataStreamWebApp.Client</SpaRoot>
<SpaProxyLaunchCommand>npm run dev</SpaProxyLaunchCommand>
<SpaProxyServerUrl>https://localhost:5173</SpaProxyServerUrl>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Microsoft.AspNetCore.SpaProxy" Version="8.0.18" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.18" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Bts.DataStreamWebApp.Client\Bts.DataStreamWebApp.Client.esproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
<ProjectReference Include="..\DataStream\DataStream.csproj" />
<ProjectReference Include="..\SharedStreams\SharedStreams.csproj" />
<ProjectReference Include="..\..\client\spark_api\spark_api.csproj" />
<ProjectReference Include="..\..\client\spark_core\spark_core.csproj" />
<ProjectReference Include="..\..\client\spark_websocket\spark_websocket.csproj" />
</ItemGroup>
<ItemGroup>
<ConfigurationProfile Include="appsettings.Development.json" />
<ConfigurationProfile Include="appsettings.Production.json" />
<ConfigurationProfile Include="appsettings.ProductionAws.json" />
<ConfigurationProfile Include="appsettings.Staging.json" />
<ConfigurationProfile Include="appsettings.json" />
</ItemGroup>
</Project>
@@ -0,0 +1,5 @@
namespace Bts.DataStreamWebApp.Server;
public class CannedQueriesConfig {
public List<CannedQuery>? Items { get; set; }
}
@@ -0,0 +1,6 @@
namespace Bts.DataStreamWebApp.Server;
public class CannedQuery {
public string? Name { get; set; }
public List<string>? Query { get; set; }
}
@@ -0,0 +1,18 @@
namespace Bts.DataStreamWebApp.Server;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class CannedQueriesController : ControllerBase {
readonly IConfiguration config;
public CannedQueriesController(IConfiguration config) =>
this.config = config;
[HttpGet(Name = "GetCannedQueries")]
public IEnumerable<CannedQuery>? Get() {
var cannedQueries = config.GetSection("CannedQueries").Get<List<CannedQuery>>()?.ToArray() ?? Array.Empty<CannedQuery>();
return cannedQueries;
}
}
@@ -0,0 +1,22 @@
// using Bts.DataStreamWebApp.Server;
// using Microsoft.AspNetCore.Mvc;
// [ApiController]
// [Route("api/[controller]")]
// public class ConfigController : ControllerBase {
// private readonly IConfiguration _configuration;
// public ConfigController(IConfiguration configuration) {
// _configuration = configuration;
// }
// [HttpGet("websocket")]
// public ActionResult<DataStreamWebSocketConfig> GetWebSocketConfig() {
// var endpoint = $"{_configuration["Kestrel:Endpoints:Http:Url"]}/ws";
// var webSocketConfig = new DataStreamWebSocketConfig {
// Endpoint = endpoint // or Https based on your setup
// };
// return Ok(webSocketConfig);
// }
// }
@@ -0,0 +1,37 @@
namespace Bts.DataStreamWebApp.Server.Controllers;
using Bts.DataStreamCore;
using Microsoft.AspNetCore.Mvc;
[Route("/ws")]
public class DataStreamSubscriptionController : ControllerBase {
readonly ILogger logger;
readonly IDataStreamFactory streamFactory;
public DataStreamSubscriptionController(ILogger<DataStreamSubscriptionController> logger, IEnumerable<IDataStreamFactory> streamFactories) {
this.logger = logger;
logger.LogInformation("Creating {ControllerType}", GetType().Name);
var streamFactoryList = streamFactories.ToList();
streamFactory = streamFactoryList.Count == 1
? streamFactoryList[0]
: new CompositeDataStreamFactory(streamFactoryList);
}
[Route("/ws")]
public async Task Get(CancellationToken cancellationToken) {
if (!HttpContext.WebSockets.IsWebSocketRequest) {
logger.LogError("Expected websocket request on path '{}'", HttpContext.Request.Path);
return;
}
logger.LogInformation("Establishing websocket connection...");
try {
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
logger.LogInformation("Websocket connection established.");
using var server = new DataStreamWebSocketServer(webSocket, streamFactory, logger);
await server.Run(cancellationToken);
} catch (Exception ex) {
logger.LogError("DataStreamSubscriptionController.Run threw exception: {Exception}", ex);
}
}
}
@@ -0,0 +1,133 @@
namespace Bts.DataStreamWebApp.Server.Controllers;
using BCG;
using bts.websocket_core.json_rpc;
using Bts.DataStreamCore;
using Bts.DataStreamWebApp.Server.Messages;
using System;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
class DataStreamSubscription : IDisposable, IHostedService {
public readonly ViewStream ViewStream;
public DataPacket? LastFired;
readonly ILogger logger;
readonly WebSocket webSocket;
public DataStreamSubscription(string id, JsonRpcId requestId, ViewStream viewStream, WebSocket webSocket, ILogger logger) {
ViewStream = viewStream;
RequestId = requestId;
Id = id;
this.webSocket = webSocket;
this.logger = logger;
}
public string Id { get; }
public JsonRpcId RequestId { get; }
public SubscriptionStatus Status { get; private set; }
public void Start() {
if (Status == SubscriptionStatus.Subscribed) {
logger.LogWarning("Ignoring redundant call to 'Start'. Id = {Id} Status = {Status}", Id, Status);
return;
}
logger.LogInformation("Subscribing to ViewStream. SubscriptionId = {SubscriptionId}", Id);
Status = SubscriptionStatus.Subscribed;
ViewStream.onChange += onChange;
}
public Task StartAsync(CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
Start();
return Task.CompletedTask;
}
public Task Clear() {
if (LastFired is null) {
return Task.CompletedTask;
}
logger.LogInformation("Clearing subscription (firing DataPacket to delete all rows). Id = {Id} Status = {Status}", Id, Status);
return sendDataPacket("clear", new(new BlueTable(LastFired.schema), new BlueTable(LastFired.schema), LastFired.newRows));
}
public void Stop() {
if (Status == SubscriptionStatus.Unsubscribed) {
logger.LogWarning("Ignoring redundant call to 'Stop'. Id = {Id} Status = {Status}", Id, Status);
return;
}
logger.LogInformation("Unsubscribing from ViewStream. SubscriptionId = {SubscriptionId}", Id);
Status = SubscriptionStatus.Unsubscribed;
ViewStream.onChange -= onChange;
}
public Task StopAsync(CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
Stop();
return Task.CompletedTask;
}
public void Dispose() =>
Stop();
async void onChange(object newValue) {
if (newValue is not DataPacket dp) {
logger.LogError("Expected DataPacket in stream callback (actually received {}).", newValue?.GetType());
return;
}
if (webSocket.State != WebSocketState.Open) {
logger.LogError(
"Unsubscribing from stream early because WebSocket state = {WebSocketState}", webSocket.State);
Dispose();
return;
}
await sendDataPacket("subscribe", dp);
}
async Task sendDataPacket(string method, DataPacket dp) {
var serializerOpts = new JsonSerializerOptions {
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
};
serializerOpts.Converters.Add(new DataPacketJsonConverter() {
IncludeCurrent = true,
UseObjectRows = true,
});
var res = JsonRpcRequest.CreateNotification(method, new DataStreamSubscriptionUpdate() {
SubscriptionId = Id,
Table = dp
});
var str = serialize(res, serializerOpts);
if (str is null) {
logger.LogError("Unsubscribing from stream early because we failed to serialize an outgoing DataPacket");
Dispose();
return;
}
logger.LogInformation(
"Sending DataPacket. NumRows = {NumRows} NumCols = {NumCols} MessageLength = {MessageLength}",
dp.newRows.Count,
dp.schema.columnNames.Length,
str.Length);
var bytes = Encoding.UTF8.GetBytes(str);
try {
await webSocket.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None);
LastFired = dp;
} catch (WebSocketException ex) {
logger.LogError($"sendDataPacket failed on webSocket.SendAsync: {ex}");
}
}
string? serialize<T>(T message, JsonSerializerOptions? opts) {
opts ??= JsonSerializerOptions.Default;
try {
return JsonSerializer.Serialize(message, opts);
} catch (Exception ex) {
logger.LogError("Failed to serialize message of type {MessageType}: {Exception}", typeof(T), ex);
}
return null;
}
}
@@ -0,0 +1,13 @@
namespace Bts.DataStreamWebApp.Server;
using BCG;
using System.Text.Json.Serialization;
public class DataStreamSubscriptionUpdate {
[JsonPropertyName("subscription_id")]
public string? SubscriptionId { get; set; }
[JsonPropertyName("table")]
public DataPacket? Table { get; set; }
}
@@ -0,0 +1,89 @@
namespace Bts.DataStreamWebApp.Server;
using Bts.PubSub;
using CommandLine;
using System.Text.Json;
using System.Text.Json.Serialization;
class DataStreamWebAppServerConfig {
/// <summary>
/// The login user for sparkd
/// </summary>
[JsonPropertyName("user")]
[Option("user", HelpText = "The sparkd login user")]
public string? User { get; set; }
/// <summary>
/// The login password for sparkd
/// </summary>
[JsonPropertyName("password")]
[Option("pass", HelpText = "The sparkd login password")]
public string? Password { get; set; }
/// <summary>
/// The config server url for sparkd
/// </summary>
[JsonPropertyName("sparkd")]
[Option("sparkd", HelpText = "The sparkd config server")]
public string? Sparkd { get; set; }
[JsonPropertyName("pubsub")]
public PubSubConfig? PubSub { get; set; }
[Option("content-root-path")]
[JsonPropertyName("content_root_path")]
public string? ContentRootPath { get; set; }
[Option("web-root-path")]
[JsonPropertyName("web_root_path")]
public string? WebRootPath { get; set; }
[Option("environment-name")]
[JsonPropertyName("environment_name")]
public string? EnvironmentName { get; set; }
public DirectoryBrowserOptions? DirectoryBrowser { get; set; }
[Option(
"config",
MetaValue = "PATH",
HelpText = "Path to json file representing these options (DataStreamPublisherConfig)")]
[JsonIgnore]
public string? ConfigFile { get; set; }
public static DataStreamWebAppServerConfig Default() =>
new() {
User = "bts",
PubSub = PubSubConfig.Default(),
};
public void MergeWith(DataStreamWebAppServerConfig other) {
User ??= other.User;
Password ??= other.Password;
PubSub ??= other.PubSub;
Sparkd ??= other.Sparkd;
ContentRootPath ??= other.ContentRootPath;
WebRootPath ??= other.WebRootPath;
ConfigFile ??= other.ConfigFile;
}
/// <summary>
/// Merges options with those defined in `ConfigFile` (does not overwrite
/// non-null existing values).
/// </summary>
/// <exception cref="InvalidOperationException">
/// JsonSerializer.Deserialize returns null
/// </exception>
public void MergeWithConfigFile() {
if (ConfigFile is null) {
return;
}
var cfgString = File.ReadAllText(ConfigFile);
var parsedCfg = JsonSerializer.Deserialize<DataStreamWebAppServerConfig>(cfgString) ??
throw new InvalidOperationException($"Failed to parse json from {ConfigFile}");
MergeWith(parsedCfg);
ConfigFile = null;
}
}
@@ -0,0 +1,5 @@
namespace Bts.DataStreamWebApp.Server;
public class DataStreamWebSocketConfig {
public string? Endpoint { get; set; }
}
@@ -0,0 +1,151 @@
namespace Bts.DataStreamWebApp.Server.Controllers;
using bts.websocket_core.json_rpc;
using Bts.DataStreamCore;
using Bts.DataStreamWebApp.Server.Messages;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
public class DataStreamWebSocketServer : IDisposable, IAsyncDisposable {
readonly ILogger logger;
readonly WebSocket webSocket;
readonly Dictionary<string, DataStreamSubscription> subscriptions = new();
readonly IDataStreamFactory streamFactory;
public DataStreamWebSocketServer(WebSocket webSocket, IDataStreamFactory streamFactory, ILogger logger) {
this.webSocket = webSocket;
this.streamFactory = streamFactory;
this.logger = logger;
}
public async Task<int> Run(CancellationToken cancellationToken) {
logger.LogInformation("DataStreamWebSocketServer running");
await foreach (var req in JsonRpcRequestAsyncEnumerable.Create(webSocket, logger, cancellationToken)) {
await (req.Method switch {
"subscribe" => onSubscribe(req, cancellationToken),
"unsubscribe" => onUnsubscribe(req.Deserialize<UnsubscribeRequest>(), cancellationToken),
_ => Task.CompletedTask,
});
}
logger.LogInformation("DataStreamWebSocketServer stopped");
return 0;
}
/// <summary>
/// Disposes internal subscriptions. To additionally notify the client of the subscription status
/// change(s), use <see cref="DisposeAsync"/>.
/// </summary>
public void Dispose() {
GC.SuppressFinalize(this);
disposeSubscriptions();
}
/// <summary>
/// Disposes internal subscriptions (like <see cref="Dispose"/>), then sends 'subscription status change'
/// notifications to the client (unlike <see cref="Dispose"/>).
/// </summary>
public async ValueTask DisposeAsync() {
GC.SuppressFinalize(this);
var finalSubs = subscriptions.Values.Select(s => (s.RequestId, s.Id)).ToList();
disposeSubscriptions();
// Send a 'subscription status change' msg to the client for each one.
foreach (var (requestId, id) in finalSubs) {
await sendSubscriptionStatus(requestId, id, "unsubscribe", SubscriptionStatus.Unsubscribed, CancellationToken.None);
}
}
async Task onSubscribe(JsonRpcRequest<JsonNode> req, CancellationToken cancellationToken) {
ArgumentNullException.ThrowIfNull(req.Id);
ArgumentException.ThrowIfNullOrEmpty(req.Method);
if (!SubscribeRequest.Parse(req, out var subReq, out var parseErr, out var errCode)) {
logger.LogError("Invalid 'subscribe' request: {Reason}", parseErr);
var rpcError = JsonRpcResponse.CreateError<SubscriptionError>(
req.Id, new() { Code = (long)errCode, Message = "Failed to parse subscription request", Data = parseErr });
await send(rpcError, cancellationToken);
return;
}
var subId = subReq.SubscriptionId ?? throw new InvalidOperationException("SubscriptionId is null (parse false)");
disposeSubscription(subId);
if (!subReq.ParseQuery(streamFactory, out var vs, out parseErr, out errCode)) {
logger.LogError("Invalid 'subscribe' request: {Reason}", parseErr);
var rpcError = JsonRpcResponse.CreateError<SubscriptionError>(
req.Id, new() { Code = (long)errCode, Message = "Failed to parse subscription request", Data = parseErr });
await send(rpcError, cancellationToken);
return;
}
if (subscriptions.TryGetValue(subId, out var oldSub)) {
logger.LogInformation("Disposing existing subscription with id {SubscriptionId}", subId);
oldSub.Dispose();
}
var sub = new DataStreamSubscription(subId, req.Id.Value, vs, webSocket, logger);
subscriptions[subId] = sub;
await sendSubscriptionStatus(req.Id.Value, subId, req.Method, SubscriptionStatus.Subscribed, cancellationToken);
vs.ErrorChanged += OnStreamError;
await sub.StartAsync(cancellationToken);
async void OnStreamError(Exception exception) {
logger.LogError("Error in ViewStream (disposing subscription): {Exception}", exception);
subscriptions.Remove(sub.Id);
sub.Dispose();
var error = new SubscriptionError() {
ErrorMessage = "Error in ViewStream (disposing subscription)"
};
if (exception is not null) {
error.ErrorMessage += ". " + exception.Message;
error.StackTrace = exception.StackTrace?.Split("\n")?.ToList();
}
var rpcError = JsonRpcResponse.CreateError<SubscriptionError>(
req.Id, new() { Code = (long)ErrorCode.ViewStreamError, Message = "Error in ViewStream (disposing subscription)", Data = error });
await send(rpcError, cancellationToken);
}
}
async Task onUnsubscribe(JsonRpcRequest<UnsubscribeRequest> req, CancellationToken cancellationToken) {
ArgumentNullException.ThrowIfNull(req.Id);
ArgumentException.ThrowIfNullOrEmpty(req.Method);
var subId = req.Params?.SubscriptionId ?? string.Empty;
if (!disposeSubscription(subId)) {
logger.LogInformation("Unable to dispose subscription. Subscription with id {Id} not found.", subId);
}
await sendSubscriptionStatus(req.Id.Value, subId, "unsubscribe", SubscriptionStatus.Unsubscribed, cancellationToken);
}
bool disposeSubscription(string subId) {
if (!subscriptions.TryGetValue(subId, out var sub)) {
return false;
}
logger.LogInformation("Disposing subscription with id {Id}", subId);
sub.Dispose();
subscriptions.Remove(subId);
return true;
}
Task sendSubscriptionStatus(JsonRpcId reqId, string subId, string method, SubscriptionStatus status, CancellationToken cancellationToken) {
logger.LogInformation("Sending {Method} ack. SubscriptionId = {SubscriptionId}", method, subId);
var response = JsonRpcResponse.Create(reqId, new SubscriptionStatusMessage() {
SubscriptionId = subId,
Status = status,
});
return send(response, cancellationToken);
}
Task send<T>(T message, CancellationToken cancellationToken) {
if (message is null) {
return Task.CompletedTask;
}
var str = JsonSerializer.Serialize(message);
var bytes = Encoding.UTF8.GetBytes(str);
logger.LogTrace("Sending data. Length = {Length}", bytes.Length);
return webSocket.SendAsync(bytes, WebSocketMessageType.Text, true, cancellationToken);
}
void disposeSubscriptions() {
logger.LogInformation("Disposing DataStreamWebSocketServer subscriptions. Count: {Count}", subscriptions.Count);
subscriptions.Values.DisposeAll();
subscriptions.Clear();
}
}
@@ -0,0 +1,11 @@
FROM bluetradingsystems/linux_dotnet7
ARG SOURCE_DIR="build/Release/bin/net7.0/linux-x64/publish"
ARG WEB_ROOT="build/Bts.DataStreamWebApp.Client/production/dist"
ARG CONF_FILE="src/DataStreamSuite/Bts.DataStreamWebApp.Server/spark.conf"
RUN dotnet dev-certs https --trust
COPY --chown=bts:bts "${SOURCE_DIR}" "bin"
COPY --chown=bts:bts "${WEB_ROOT}" "bin/wwwroot"
COPY --chown=bts:bts "${CONF_FILE}" ".local/share/DataStream/conf/spark.conf"
WORKDIR "/home/bts/bin"
@@ -0,0 +1,5 @@
namespace Bts.DataStreamWebApp.Server;
public class CannedQueriesResponse {
public List<CannedQuery>? Items { get; set; }
}
@@ -0,0 +1,7 @@
namespace Bts.DataStreamWebApp.Server.Messages;
enum ErrorCode {
None = 0,
InvalidSubscriptionRequest,
ViewStreamError,
}
@@ -0,0 +1,78 @@
namespace Bts.DataStreamWebApp.Server.Messages;
using BCG;
using bts.websocket_core.json_rpc;
using Bts.DataStreamCore;
using Bts.DataStreamExpressions;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
public class SubscribeRequest {
[JsonPropertyName("query")]
public string? Query { get; set; }
[JsonPropertyName("subscription_id")]
public string? SubscriptionId { get; set; }
internal static bool Parse(JsonRpcRequest<JsonNode> req, [NotNullWhen(true)]out SubscribeRequest? subscription, [NotNullWhen(false)]out SubscriptionError? error, out ErrorCode errorCode) {
subscription = null;
errorCode = ErrorCode.None;
var paramsJsonObj = req.Params?.AsObject();
error = null;
if (paramsJsonObj is null) {
errorCode = ErrorCode.InvalidSubscriptionRequest;
error = new() { ErrorMessage = "'params' object not found" };
return false;
}
subscription = paramsJsonObj.Deserialize<SubscribeRequest>();
if (subscription is null) {
errorCode = ErrorCode.InvalidSubscriptionRequest;
error = new() { ErrorMessage = "'params' object invalid" };
return false;
}
return true;
}
internal bool ParseQuery(
IDataStreamFactory streamFactory,
[NotNullWhen(true)] out ViewStream? stream,
[NotNullWhen(false)] out SubscriptionError? error,
out ErrorCode errorCode
) {
stream = null;
errorCode = ErrorCode.None;
error = null;
if (Query is null) {
errorCode = ErrorCode.InvalidSubscriptionRequest;
error = new() { ErrorMessage = "'params.query' field not found (expected a string)" };
return false;
}
var exp = DataStreamExpressionsInterpreter.Evaluate(Query);
if (exp is null) {
errorCode = ErrorCode.InvalidSubscriptionRequest;
error = new() { ErrorMessage = "'params.query' expression has invalid syntax" };
return false;
}
Exception? exception = null;
try {
stream = exp.getView(new Dictionary<string, StatementExpression>(0), streamFactory);
} catch (Exception ex) {
exception = ex;
}
if (stream is null) {
errorCode = ErrorCode.InvalidSubscriptionRequest;
error = new() { ErrorMessage = "Failed to create ViewStream from 'params.query' expression (check for typos in arguments/typenames)" };
if (exception is not null) {
error.ErrorMessage += ". " + exception.Message;
error.StackTrace = exception.StackTrace?.Split("\n")?.ToList();
}
return false;
}
return true;
}
}
@@ -0,0 +1,11 @@
namespace Bts.DataStreamWebApp.Server.Messages;
using System.Text.Json.Serialization;
public class SubscriptionError {
[JsonPropertyName("error_message")]
public string? ErrorMessage { get; set; }
[JsonPropertyName("stack_trace")]
public List<string>? StackTrace { get; set; }
}
@@ -0,0 +1,18 @@
namespace Bts.DataStreamWebApp.Server.Messages;
using System.Text.Json.Serialization;
enum SubscriptionStatus {
None,
Subscribed,
Unsubscribed,
}
class SubscriptionStatusMessage {
[JsonPropertyName("subscription_id")]
public string? SubscriptionId { get; set; }
[JsonPropertyName("status")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public SubscriptionStatus Status { get; set; } = SubscriptionStatus.None;
}
@@ -0,0 +1,8 @@
namespace Bts.DataStreamWebApp.Server.Messages;
using System.Text.Json.Serialization;
public class UnsubscribeRequest {
[JsonPropertyName("subscription_id")]
public string? SubscriptionId { get; set; }
}
@@ -0,0 +1,87 @@
using Bts.DataStreamCore.Services;
using Bts.PubSub.Services;
using Bts.SharedStreams.Services;
using Spark.Api.Services;
using Spark.Core.Services;
using System.Reactive.Linq;
using System.Reflection;
var builder = WebApplication.CreateBuilder(new WebApplicationOptions {
Args = args,
ApplicationName = "Bts.DataStreamWebApp.Server",
});
ConfigureApp(builder.Configuration, builder.Environment.EnvironmentName);
ConfigureServices(builder.Services, builder.Configuration);
var app = builder.Build();
var wsOpts = new WebSocketOptions {
KeepAliveInterval = TimeSpan.FromMinutes(2)
};
if (app.Environment.IsDevelopment()) {
app.UseDirectoryBrowser("/browse");
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseWebSockets(wsOpts);
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthorization();
app.MapControllers();
try {
Console.Write($"Starting Application");
app.Run();
Console.Write($"Application exited gracefully.");
} catch (Exception ex) {
Console.Error.Write($"Application crashed: {ex}");
return 1;
}
return 0;
static void ConfigureApp(ConfigurationManager configBuilder, string environment) {
// We prefix appsettings with the exe name because multiple exes will usually be in the build dir
// (this way, each exe can still load its own appsettings files from the current directory).
var exeName = Assembly.GetCallingAssembly().GetName().Name;
AddJsonFile($"{exeName}.appsettings.json");
AddJsonFile($"{exeName}.appsettings.{environment}.json");
var cfg = configBuilder.GetSection("config")?.Value;
if (!SetBasePath(cfg)) {
AddJsonFile(cfg);
}
bool SetBasePath(string? path) {
if (!Directory.Exists(path)) {
return false;
}
configBuilder.SetBasePath(path);
return true;
}
bool AddJsonFile(string? path) {
if (!File.Exists(path)) {
return false;
}
configBuilder.AddJsonFile(path);
return true;
}
}
static void ConfigureServices(IServiceCollection services, IConfiguration config) {
services.AddDirectoryBrowser()
.AddControllers().Services
.AddEndpointsApiExplorer()
.AddSwaggerGen();
services.AddSpaStaticFiles();
services.AddSpark(config.GetSection("Spark"))
.AddSparkApi(config.GetSection("SparkApi"))
.AddMasterWorker(config.GetSection("MasterWorker"))
.AddViewStreamDatabase()
.AddPrimitiveStreams(config.GetSection("PrimitiveStreams"))
.AddMarketDataStreams()
.AddNaturalLanguageStreams(config.GetSection("NaturalLanguageStream"))
.AddCompositeDataStreamFactory()
.AddPubSubClient(config.GetSection("PubSub"));
}
@@ -0,0 +1,29 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:8080",
"environmentVariables": {
"ASPNETCORE_HTTP_PORT": "8080",
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy",
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:8080;https://localhost:8443",
"environmentVariables": {
"ASPNETCORE_HTTP_PORT": "8080",
"ASPNETCORE_HTTPS_PORT": "8443",
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy",
}
},
},
}
@@ -0,0 +1,52 @@
{
"PubSub": {
"Routing": {
"PublisherRoutes": [
{
"Name": "default",
"ServerAddress": "bts-cloud://localhost:22019",
"TagRegExp": ".*"
}
],
"SubscriberRoutes": [
{
"Name": "default",
"ServerAddress": "bts-cloud://localhost:22019",
"TagRegExp": ".*"
}
]
}
},
"CannedQueries": [
{
"Name": "BBO",
"Query": [
"bbo"
]
},
{
"Name": "Trades",
"Query": [
"timesales"
]
},
{
"Name": "Instruments",
"Query": [
"instruments"
]
},
{
"Name": "Books",
"Query": [
"books"
]
},
{
"Name": "Benzinga News",
"Query": [
"benzinga_news"
]
}
]
}
@@ -0,0 +1,45 @@
{
"NaturalLanguagePrompt": {
"prompts": [
{
"category": "Impact of financial events on the market",
"description": "Show how trading metrics (e.g., price movement, volume) are affected by events related to finance (rate announcements, earnings reports, etc.).",
"prompt": "Show me the price movement and volume of Crude Oil futures during the 5-minute window following the last 5 OPEC meetings",
"examples": [
{
"natural_language": "Show me the price movement and volume of Crude Oil futures during the 5-minute window following the last 5 OPEC meetings",
"dsl": [
"time_idx=EventTimeIndex.OpecMeetings(which: \"last 5\", duration: \"+5min\")",
"contract_idx=ActiveFutureContracts(predicate: \"product==CL\")",
"Bars(time_index: time_idx, contract_index: contract_idx, fields: [\"price_movement\", \"volume\"])"
]
},
{
"natural_language": "How do OPEC meetings affect the price of Crude futures? Examine the 10 minutes after each meeting this year.",
"dsl": [
"time_idx=EventTimeIndex.OpecMeetings(duration: \"+10min\", predicate: \"timestamp.year==2024\")",
"contract_idx=ActiveFutureContracts(predicate: \"product==CL\")",
"Bars(time_index: time_idx, contract_index: contract_idx, fields: [\"price_movement\", \"volume\"])"
]
},
{
"natural_language": "Show me how the SP 500 price moves after FOMC rate announcements that day",
"dsl": [
"time_idx=EventTimeIndex.FomcRateAnnouncements(which: \"timestamp.year==2024\", duration: \"+10min\")",
"contract_idx=ActiveFutureContracts(predicate: \"product==ES\")",
"Bars(time_index: time_idx, contract_index: contract_idx, fields: [\"price_movement\", \"volume\"])"
]
},
{
"natural_language": "Analyze the impact of the FED's interest rate hikes in 2022 on the 10-year Treasury yield, focusing on the 2-week period following each hike.",
"dsl": [
"time_idx=FedInterestRateAnnouncements(predicate: \"timestamp.year==2022 && change_pct>0\", duration: \"+2 weeks\")",
"contract_idx=ActiveFutureContracts(predicate: \"product==ZN\")",
"Bars(time_index: time_idx, contract_index: contract_idx)"
]
}
]
}
]
}
}
@@ -0,0 +1,8 @@
{
"Spark": {
"ConfigServer": "http://sparkd:8000/config"
},
"SparkApi": {
"IsEnabled": true
}
}
@@ -0,0 +1,115 @@
{
"MasterWorker": {
"Interval": "00:00:00.1"
},
"ViewStreamDatabase": {
"Interval": "00:00:00.1"
},
"Spark": {
"ConfigServer": "http://172.30.2.181:8000/config"
},
"SparkApi": {
"Credentials": {
"User": "bts",
"Password": ""
},
"EnableAdmin": true,
"EnableOrders": true,
"EnablePosition": true
},
"PubSub": {
"HeartbeatInterval": 15000,
"MissingHeartbeatsBeforeGc": 50,
"ServerPort": 22019,
"Routing": {
"PublisherRoutes": [
{
"Name": "default",
"ServerAddress": "bts-cloud://172.30.2.181:22019",
"TagRegExp": ".*"
}
],
"SubscriberRoutes": [
{
"Name": "default",
"ServerAddress": "bts-cloud://172.30.2.181:22019",
"TagRegExp": ".*"
}
]
},
"TagTypeToAssemblyMap": {
"Entries": [
{
"HandlerClass": "BCG.PubSubServerTagHandler",
"Assembly": "DataStreamCore.dll"
}
]
},
"TagHandlerFactory": {
"Handlers": [
{
"TagType": "DataStream",
"Assembly": "DataStreamCore",
"Class": "bts.PubsubServerStreamTagHandler"
},
{
"TagType": "System_Sub",
"Assembly": "PubSub",
"Class": "BCG.SystemSubscriptionMapPublisher"
},
{
"TagType": "System_Stats",
"Assembly": "PubSub",
"Class": "BCG.SystemStatsPublisher"
},
{
"TagType": ".*",
"Assembly": "DataStreamCore",
"Class": "bts.PubsubServerGenericDataHandler"
}
]
}
},
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://172.30.2.181:8080"
},
"Https": {
"Url": "https://172.30.2.181:8443"
}
}
},
"CannedQueries": [
{
"Name": "BBO",
"Query": [
"bbo"
]
},
{
"Name": "Trades",
"Query": [
"timesales"
]
},
{
"Name": "Instruments",
"Query": [
"instruments"
]
},
{
"Name": "Books",
"Query": [
"books"
]
},
{
"Name": "Benzinga News",
"Query": [
"benzinga_news"
]
}
]
}
@@ -0,0 +1,44 @@
{
"CannedQueries": [
{
"Name": "BBO",
"Query": [
"bbo"
]
},
{
"Name": "Trades",
"Query": [
"timesales"
]
},
{
"Name": "Instruments",
"Query": [
"instruments"
]
},
{
"Name": "Books",
"Query": [
"books"
]
},
{
"Name": "Benzinga News",
"Query": [
"benzinga_news"
]
}
],
"Spark": {
"ConfigServer": "http://sparkd:8000/config"
},
"SparkApi": {
"IsEnabled": true,
"Credentials": {
"User": "bts",
"Password": ""
}
}
}
@@ -0,0 +1,443 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://0.0.0.0:8080"
},
"HttpIpv6": {
"Url": "http://[::1]:8080"
},
"Https": {
"Url": "https://0.0.0.0:8443"
},
"HttpsIpv6": {
"Url": "https://[::1]:8443"
}
}
},
"MasterWorker": {
"Interval": "00:00:00.1"
},
"ViewStreamDatabase": {
"Interval": "00:00:00.1"
},
"PrimitiveStream": {
"Assemblies": [
"DataStream.dll",
"SharedStreams.dll"
],
"ExcludedTypes": [
"SettlementPriceStream",
"OHLCVStream"
]
},
"StreamDatabase": {
"Path": "/home/bts/.local/share/bts/silver_pine/store.db",
"Query": "select * from metadata"
},
"NaturalLanguageStream": {
"prompts": [
{
"category": "Settlement",
"MulitLineDescription": [
"This is a few shot learning example for an NL to DSL. The DSL function in question is Settlement (symbol_or_contract, datetime1). ",
"The first argument is a future (like CL, for crude oil) or a specific future contract like crude oil Dec 24 future (CLZ4). ",
"The second argument is the date at which the settlement is to be fetched. There is an overloaded function, Settlement ",
"(symbol_or_contract, begin_dt, end_dt) which gets the settlements for all dates from begin_dt to end_dt. This will also have ",
"datetimes expressed as Today, Yesterday, Last Friday, First Friday of the month, Last Thursday of August etc. You will need to know ",
"what Today is, since there will be referecnes to last week, yesterday etc. These examples are assuming Today is 10/11/2024, however ",
"you should use the Today's real date"
],
"prompt": "What was the settlement for SP 500 Future on June 8th?",
"examples": [
{
"natural_language": "Find Settlement price of the SP500 future on 04/22/2023.",
"dsl": "Settlement(\"ES\", 4/22/2023)"
},
{
"natural_language": "Find Settlement price of the Silver Dec 24 Future on 04/22/2023.",
"dsl": "Settlement(\"SIZ4\", 4/22/2023)"
},
{
"natural_language": "Find Yesterday's Settlement of the Crude Oil Future.",
"dsl": "Settlement(\"CL\", 10/10/2024)"
},
{
"natural_language": "Find today's settlement of the 10 year treasury future.",
"dsl": "Settlement(\"ZN\", 10/11/2024)"
},
{
"natural_language": "Find the Settlement of Natural Gas Dec 24 future on the first Friday of Sept.",
"dsl": "Settlement(\"NGZ4\", 9/6/2024)"
},
{
"natural_language": "Find the Settlement of WTI Jan 25 future in the first Week of Sept.",
"dsl": "Settlement(\"CLF5\", 9/2/2024, 9/6/2024)"
},
{
"natural_language": "Find the Settlement price for the Sp 500 Dec 24 Mar 25 spread for the first two weeks of September.",
"dsl": "Settlement(\"ESZ4-ESH5\", 9/14/2024, 9/1/2024)"
},
{
"natural_language": "Find the Settlement of Natural Gas future on the first Friday of Sept.",
"dsl": "Settlement(\"NG\", 9/6/2024)"
}
]
},
{
"category": "BBO",
"MulitLineDescription": [
"This is a few shot learning example for an NL to DSL. The DSL function in question is Bbo(args...). ",
"It accepts the following parameters: ",
"symbol (a product code such as ZN for 10 year T-Note futures), ",
"contract (a specific contract, such as ZNZ4 for the 10 Year T-Note future Dec 2024 exp), ",
"venue (name of the exchange, such as CME, NASDAQ, NYSE, NYMEX, DERIBIT etc.), ",
"interval (sample frequency of the data), ",
"begin and end (inclusive beginning and end of the time window to query, respectively), ",
"Arguments are specified by name, for example: Bbo(symbol: \"ZN\", venue: \"CME\", interval: \"PT1M\", begin: \"2024-10-10\", end: \"2024-10-11\"). ",
"Depending on the wording of the request, some arguments may be omitted. ",
"The begin, end, and interval args should be expressed in iso 8601 format. ",
"Interpret dates according to the trading schedule rather than calendar date whenever possible. ",
"In some prompts, the time window will be expressed in relative terms (e.g., \"last Monday\", or \"yesterday\") ",
"which must be translated to exact dates. To correctly interpret the dsl in the examples, assume the current time is 2024-10-11T12:00-05:00."
],
"prompt": "Show me bbo for SP 500 futures on Sep 23rd of this year.",
"examples": [
{
"natural_language": "Get SP500 future bbo from Wednesday.",
"dsl": "Bbo(symbol: \"ES\", venue: \"CME\", begin: \"2024-10-08T17:00:00-06:00\", end: \"2024-10-09T16:00:00-06:00\")"
},
{
"natural_language": "Get bbo for 10 year T-Notes in the first minute after the market opened yesterday.",
"dsl": "Bbo(symbol: \"ES\", venue: \"CME\", begin: \"2024-10-10T17:00:00-06:00\", end: \"2024-10-10T17:01:00-06:00\", exclusive_end: true)"
},
{
"natural_language": "Retrieve NVIDIA L1 books from Oct 3",
"dsl": "Bbo(symbol: \"NVDA\", venue: \"NASDAQ\", begin: \"2024-10-02TT18:00:00-05:00\", end: \"2024-10-03T17:00:00-05:00\")"
},
{
"natural_language": "Show me L1 data for Crude oil futures this week",
"dsl": "Bbo(symbol: \"CL\", venue: \"CME\", begin: \"2024-10-06:T17:00:00-06:00\")"
},
{
"natural_language": "Fetch hourly bbo for Gold futures this week",
"dsl": "Bbo(symbol: \"GC\", venue: \"CME\", begin: \"2024-10-06:T17:00:00-06:00\", interval: \"PT1H\")"
},
{
"natural_language": "What was the minute by minute bbo for Tesla yesterday?",
"dsl": "Bbo(symbol: \"TSLA\", venue: \"NASDAQ\", begin: \"2024-10-09T18:00:00-05:00\", end: \"2024-10-10T17:00:00-05:00\", interval: \"PT1M\")"
}
]
},
{
"category": "Order flow imbalance",
"MulitLineDescription": [
"This is a few shot learning example for an NL to DSL. The DSL function in question is OrderImbalance(args...). ",
"It accepts the following parameters: ",
"symbol (a product code such as ZN for 10 year T-Note futures), ",
"contract (a specific contract, such as ZNZ4 for the 10 Year T-Note future Dec 2024 exp), ",
"venue (name of the exchange, such as CME, NASDAQ, NYSE, NYMEX, DERIBIT etc.), ",
"interval (sample frequency of the data), ",
"levels (the number of price levels to examine)",
"begin and end (inclusive beginning and end of the time window to query, respectively), ",
"inclusive_end (a bool flag that, when true, makes the end time inclusive). ",
"Arguments are specified by name, for example: OrderImbalance(symbol: \"ZN\", venue: \"CME\", interval: \"PT1M\", begin: \"2024-10-10\", end: \"2024-10-11\"). ",
"Depending on the wording of the request, some arguments may be omitted. ",
"The begin, end, and interval args should be expressed in iso 8601 format. ",
"Interpret dates according to the trading schedule rather than calendar date whenever possible. ",
"In some prompts, the time window will be expressed in relative terms (e.g., \"last Monday\", or \"yesterday\") ",
"which must be translated to exact dates. To correctly interpret the dsl in the examples, assume the current time is 2024-10-11T12:00-05:00."
],
"examples": [
{
"natural_language": "Get SP500 order imbalance from Wednesday.",
"dsl": "OrderImbalance(symbol: \"ES\", venue: \"CME\", begin: \"2024-10-08T17:00:00-06:00\", end: \"2024-10-09T16:00:00-06:00\", \"PT1M\")"
},
{
"natural_language": "Get SP500 order imbalance from Wednesday for the top 5 price levels.",
"dsl": "OrderImbalance(symbol: \"ES\", venue: \"CME\", begin: \"2024-10-08T17:00:00-06:00\", end: \"2024-10-09T16:00:00-06:00\", levels: 5, interval: \"PT1M\")"
},
{
"natural_language": "Get orderflow imbalance for 10 year T-Notes in the first minute after the market opened yesterday.",
"dsl": "OrderImbalance(symbol: \"ES\", venue: \"CME\", begin: \"2024-10-10T17:00:00-06:00\", end: \"2024-10-10T17:01:00-06:00\", interval: \"PT1S\")"
},
{
"natural_language": "Retrieve NVIDIA order flow from Oct 3",
"dsl": "OrderImbalance(symbol: \"NVDA\", venue: \"NASDAQ\", begin: \"2024-10-02TT18:00:00-05:00\", end: \"2024-10-03T17:00:00-05:00\", interval: \"PT1S\")"
},
{
"natural_language": "Show me order imbalance for Crude oil futures this week",
"dsl": "OrderImbalance(symbol: \"CL\", venue: \"CME\", begin: \"2024-10-06:T17:00:00-06:00\", interval: \"PT1H\")"
},
{
"natural_language": "Fetch hourly orderflow for Gold futures this week",
"dsl": "OrderImbalance(symbol: \"GC\", venue: \"CME\", begin: \"2024-10-06:T17:00:00-06:00\", interval: \"PT1H\")"
},
{
"natural_language": "What was the minute by minute orderflow for Tesla yesterday?",
"dsl": "OrderImbalance(symbol: \"TSLA\", venue: \"NASDAQ\", begin: \"2024-10-09T18:00:00-05:00\", end: \"2024-10-10T17:00:00-05:00\", interval: \"PT1M\")"
}
]
},
{
"category": "VWAP",
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is VWAP (symbol_or_contract, datetime1, datetime2). The first argument is a future (like CL, for crude oil, or ES for SP 500) or a specific future contract like crude oil Dec 24 future (CLZ4) or SP 500 Dec 2024 (ESZ4). The second argument is where the VWAP has to be calculated (also the beginning of the time window), and the third argument is datetime specifying the end of the time window. So, for example, a 20-day VWAP on 9/30/2023, the first argument will be 9/30/2023, and the third argument will be 9/10/2023 (20 days prior). This will also have datetimes expressed as Today, Yesterday, Last Friday, Last Sunday, etc. For the purpose of this few-shot prompts, let's assume today is 10/11/2024.",
"prompt": "What was the VWAP for SP 500 Future over the past 30 days?",
"examples": [
{
"natural_language": "Find the 20 day VWAP of the SP future on 04/22/2023.",
"dsl": "VWAP(\"ES\", 4/22/2023, 4/02/2023)"
},
{
"natural_language": "Find Yesterday's 10 day VWAP of the Crude Oil Future.",
"dsl": "VWAP(\"CL\", 10/10/2024, 10/1/2024)"
},
{
"natural_language": "Find today's 30-day VWAP of the 10 year treasury future.",
"dsl": "VWAP(\"ZN\", 10/11/2024, 9/11/2024)"
},
{
"natural_language": "Find the 10 hour VWAP of the Natural Gas future from yesterday at 5:00 PM.",
"dsl": "VWAP(\"NG\", 9/27/2024 17:00:00, 9/27/2024 7:00:00)"
}
]
},
{
"category": "Price Diff",
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is OHLCVPriceDiff (symbol_or_contract, datetime1, datetime2). The first argument is a future (like CL, for crude oil) or a specific future contract like crude oil Dec 24 future (CLZ4). The second and third arguments are dates, that the function finds the difference of prices at. Assume today is 10/15/2024",
"prompt": "What is the difference in SP 500 Future from July 10th to July 14th?",
"examples": [
{
"natural_language": "What is the difference in ZN price from July 10th to July 14th?",
"dsl": "OHLCVPriceDiff(\"ZN\", 7/14/2024, 7/10/2024)"
},
{
"natural_language": "What is the price difference in SP Future price from Sept 10th and Sept 5th?",
"dsl": "OHLCVPriceDiff(\"ES\", 9/10/2024, 9/5/2024)"
},
{
"natural_language": "What is the price difference in Natural Gas Dec Future price from Aug 30th and Aug 20th?",
"dsl": "OHLCVPriceDiff(\"NGZ4\", 8/30/2024, 8/20/2024)"
}
]
},
{
"category": "Absolute Volume",
"description": "this is a few shot learning example for an NL to DSL. The DSL function in question is AbsVolume(symbol, ContractType, datetime1, datetime2), which computes the highest volume in the date range, specified by datetime1, and datetime2. The first argument is a future symbol (like CL for crude oil, ES for SP 500 etc). The second argument is the ContractType, which is \"Future\" for futures and \"FutureSpread\" for future spreads. the 3rd argument is the begin date and the 4th argument is the end date, between which the max volume is computed. If no future is specified use \"*\" from the first arugment. For this prompt, assume today is 10/11/2024 ",
"prompt": "Which SP 500 Future traded the most between 7/14/2024 and 7/10/2024",
"examples": [
{
"natural_language": "Which SP Future traded the most between 7/14/2024 and 7/10/2024",
"dsl": "AbsVolume(\"ES\", \"Future\", 7/14/2024, 7/10/2024)"
},
{
"natural_language": "Which Gold Spread traded the most between Today and Sept 20th",
"dsl": "AbsVolume(\"GC\", \"FutureSpread\", 10/1/2024, 9/20/2024)"
},
{
"natural_language": "Which 10 year Spread traded the highest volume last week?",
"dsl": "AbsVolume(\"ZN\", \"FutureSpread\", 9/27/2024, 9/23/2024)"
},
{
"natural_language": "Which Corn Spread traded the most today?",
"dsl": "AbsVolume(\"ZC\", \"FutureSpread\", 10/1/2024, 10/1/2024)"
},
{
"natural_language": "Which Gold Future traded the most today?",
"dsl": "AbsVolume(\"GC\", \"Future\", 9/30/2024, 9/30/2024)"
}
]
},
{
"category": "Roll",
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is FuturesRoll(symbol) which computes the rollover date for a future, ie when the highest volume swtches from one expiration to the next one. ",
"prompt": "When does the roll typically occur In SP 500?",
"examples": [
{
"natural_language": "When does the roll typically occur In CL?",
"dsl": "FuturesRoll(\"CL\")"
},
{
"natural_language": "When does the roll typically occur for 10 year treasury?",
"dsl": "FuturesRoll(\"ZN\")"
},
{
"natural_language": "When does the roll usually occur for Gold Future",
"dsl": "FuturesRoll(\"GC\")"
},
{
"natural_language": "At what date does the future rollover happens for SP500?",
"dsl": "FuturesRoll(\"ES\")"
},
{
"natural_language": "When does the future rollover happens for the Corn Future?",
"dsl": "FuturesRoll(\"ZC\")"
}
]
},
{
"category": "Daily Move",
"description": "This is a few shot learning example for an NL to DSL. ",
"prompt": "How often did Sp 500 Future settle + or - 10% over the previous day?",
"examples": [
{
"natural_language": "List all the days when the SP 500 closed + or -5% from the previous day's close",
"dsl": "ColDiff(filteredOHLCV(\"ES\"),\"ts_event\", -1, \"close\", \"close\", \"close_diff\", 5)"
},
{
"natural_language": "List all the days when the Crude Oil closed + or -2.5% from the previous day's close",
"dsl": "ColDiff(filteredOHLCV(\"CL\"),\"ts_event\", -1, \"close\", \"close\", \"close_diff\", 2.5)"
},
{
"natural_language": "Show me all the days when the Ten year Treaury Future opened + or -1% from the previous day's open",
"dsl": "ColDiff(filteredOHLCV(\"ZN\"),\"ts_event\", -1, \"open\", \"open\", \"open_diff\", 1.0)"
},
{
"natural_language": "List all the trading days when the Silver Future opened + or -1% from the previous day's close",
"dsl": "ColDiff(filteredOHLCV(\"SI\"),\"ts_event\", -1, \"open\", \"open\", \"open_diff\", 1.0)"
}
]
},
{
"category": "Average Volume",
"description": "This is a few shot learning example for an NL to DSL. For the purpose of this few shot prompts, lets assume today is 10/3/2024 ",
"prompt": "When does the most open interest typically shift from one contract to another in SP 500 Future?",
"examples": [
{
"natural_language": "When does the most open interest typically shift from one contract to another in e-minis? ",
"dsl": "FuturesRoll(\"ES\")"
},
{
"natural_language": "Usually when does the open interest move from one contract to another in WTI?",
"dsl": "FuturesRoll(\"CL\")"
}
]
},
{
"category": "Events",
"description": "This is a few shot learning example for an NL to DSL. In the NL prompt the user is asking to show the price of a future (treasury future, or SP 500 future etc) around an event type. The event type could be \"FOMC Announcement\" or \"Storage numbers\" etc. The DSL is as follows: EventsPriceAction(future_name, event_type, pre_event_window, post_event_window, number_of_events) first arg is the name of the future, (ex ES or ZN etc), the second argument is the event type, for example, \"FOMC announcement\", the 3rd argument is a time window for how much pre-event data to include in the end result, the 4th argument is how much post-event data to include in the end result. Both are strings of the form \"5d\" for 5 days, \"5h\" for 5 hours and \"120m\" for 120 minutes and so on. Usually, only one of the event periods is provided. Set the other to be half of what the user provided. The last argument is the number of events to consider. So if it’s 7, that means the most recent 7 events. If the user does not specify that, pass a large number.",
"prompt": "What did the volume and price of SP500 Future do following the release of the last 10 FOMC rates?",
"examples": [
{
"natural_language": "How did SP 500 Future do in the 5 days following the FOMC announcements?",
"dsl": "EventsPriceAction(\"ES\", \"FOMC Announcement\", \"2d\", \"5d\", 10)"
},
{
"natural_language": "How did the price of Treasury Future evolve in the 2 days following the Job Reports?",
"dsl": "EventsPriceAction(\"ZN\", \"Job Reports\", \"1d\", \"2d\", 10)"
},
{
"natural_language": "Show me the price movement and volume of Crude Oil futures during the 5-minute window following the last 5 OPEC meetings.",
"dsl": "EventsPriceAction(\"CL\", \"OPEC Meeting\", \"2m\", \"5m\", 5)"
},
{
"natural_language": "Show me the price movement and volume of SP 500 futures preceding the 10-minute window of the last 5 OPEC meetings.",
"dsl": "EventsPriceAction(\"CL\", \"OPEC Meeting\", \"10m\", \"5m\", 5)"
}
]
},
{
"category": "Indicator, Standard Deviation",
"MulitLineDescription": [
"This is a few shot learning example for an NL to DSL. In the NL prompt the user is asking to show all the days when ",
"the price of a future (treasury future, or SP 500 future etc) moved more than m times the N days standard deviation, ",
"where both m and N are provided. For example, the DSL for \"Using a 90d standard deviation, give me the dates and ",
"amounts of the last 4+ standard deviation moves in WTI futures\" would be: ",
"r=standardDeviation(\"ES\", \"90d\")\n",
"r=math(r, \"abs_daily_change = Abs(close - open) comp = std_dev * 4\")\n",
"r=filter(r,\"abs_daily_change >= comp\"). ",
"The first line, creates a stream for StandardDeviation, based on the number of days, for example 90d standard deviation, ",
"and assigned it to r, the second line takes r from the first line and passes it to a math stream that creates 2 new columns, ",
"abs_daily_change and comp. comp is the factor (4, from 4+ in the prompt) times standard deviation. The third line filters ",
"(like a sql where) of rows where abs_daily_change >= comp"
],
"prompt": "Using a 90d standard deviation, give me the dates and amounts of the last 1+ standard deviation moves in SP futures ?",
"examples": [
{
"natural_language": "Using a 90d standard deviation, give me the dates and amounts of the last 1+ standard deviation moves in WTI futures ?",
"dsl": "r=standardDeviation(\"CL\", \"90d\")\nr=math(r, \"abs_daily_change = Abs(close - open) comp = std_dev * 1.0\")\nr=filter(r,\"abs_daily_change >= comp\")"
},
{
"natural_language": "Show me the times the SP Futures moved twice the 60 day standard deviation",
"dsl": "r=standardDeviation(\"ES\", \"60d\")\nr=math(r, \"abs_daily_change = Abs(close - open) comp = std_dev * 2.0\")\nr=filter(r,\"abs_daily_change >= comp\")"
},
{
"natural_language": "List all the days the Gold Futures moved 1.5 times the 45 day standard deviation",
"dsl": "r=standardDeviation(\"GC\", \"45d\")\nr=math(r, \"abs_daily_change = Abs(close - open) comp = std_dev * 1.5\")\nr=filter(r,\"abs_daily_change >= comp\")"
},
{
"natural_language": "List all the days the 10 year treasury future moved more than twice the 1 month standard deviation",
"dsl": "r=standardDeviation(\"ZN\", \"30d\")\nr=math(r, \"abs_daily_change = Abs(close - open) comp = std_dev * 2.0\")\nr=filter(r,\"abs_daily_change >= comp\")"
}
]
}
]
},
"SparkApi": {
"IsEnabled": false,
"Credentials": {
"User": "bts",
"Password": ""
}
},
"PubSub": {
"HeartbeatInterval": 15000,
"MissingHeartbeatsBeforeGc": 50,
"ServerPort": 22019,
"Routing": {
"PublisherRoutes": [
{
"Name": "default",
"ServerAddress": "bts-cloud://pubsub:22019",
"TagRegExp": ".*"
}
],
"SubscriberRoutes": [
{
"Name": "default",
"ServerAddress": "bts-cloud://pubsub:22019",
"TagRegExp": ".*"
}
]
},
"TagTypeToAssemblyMap": {
"Entries": [
{
"HandlerClass": "BCG.PubSubServerTagHandler",
"Assembly": "DataStreamCore.dll"
}
]
},
"TagHandlerFactory": {
"Handlers": [
{
"TagType": "DataStream",
"Assembly": "DataStreamCore",
"Class": "bts.PubsubServerStreamTagHandler"
},
{
"TagType": "System_Sub",
"Assembly": "PubSub",
"Class": "BCG.SystemSubscriptionMapPublisher"
},
{
"TagType": "System_Stats",
"Assembly": "PubSub",
"Class": "BCG.SystemStatsPublisher"
},
{
"TagType": ".*",
"Assembly": "DataStreamCore",
"Class": "bts.PubsubServerGenericDataHandler"
}
]
}
}
}
@@ -0,0 +1,15 @@
Logging {
value {
console {
default_level: "Info"
}
syslog {
default_level: "Info"
}
file {
default_level: "Info"
}
initial_queue_size: 120001
max_queue_growth: 4
}
}
@@ -0,0 +1,168 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style>
table {
border: 0
}
.commslog-data {
font-family: Consolas, Courier New, Courier, monospace;
}
.commslog-server {
background-color: red;
color: white
}
.commslog-client {
background-color: green;
color: white
}
</style>
</head>
<body>
<h1>ALT WebSocket Sample Application</h1>
<p id="stateLabel">Ready to connect...</p>
<div>
<label for="connectionUrl">WebSocket Server URL:</label>
<input id="connectionUrl" />
<button id="connectButton" type="submit">Connect</button>
</div>
<p></p>
<div>
<label for="sendMessage">Message to send:</label>
<input id="sendMessage" disabled />
<button id="sendButton" type="submit" disabled>Send</button>
<button id="closeButton" disabled>Close Socket</button>
</div>
<h2>Communication Log</h2>
<table style="width: 800px">
<thead>
<tr>
<td style="width: 100px">From</td>
<td style="width: 100px">To</td>
<td>Data</td>
</tr>
</thead>
<tbody id="commsLog">
</tbody>
</table>
<script>
var connectionUrl = document.getElementById("connectionUrl");
var connectButton = document.getElementById("connectButton");
var stateLabel = document.getElementById("stateLabel");
var sendMessage = document.getElementById("sendMessage");
var sendButton = document.getElementById("sendButton");
var commsLog = document.getElementById("commsLog");
var closeButton = document.getElementById("closeButton");
var socket;
var scheme = document.location.protocol === "https:" ? "wss" : "ws";
var port = document.location.port ? (":" + document.location.port) : "";
connectionUrl.value = scheme + "://" + document.location.hostname + port + "/ws" ;
function updateState() {
function disable() {
sendMessage.disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
function enable() {
sendMessage.disabled = false;
sendButton.disabled = false;
closeButton.disabled = false;
}
connectionUrl.disabled = true;
connectButton.disabled = true;
if (!socket) {
disable();
} else {
switch (socket.readyState) {
case WebSocket.CLOSED:
stateLabel.innerHTML = "Closed";
disable();
connectionUrl.disabled = false;
connectButton.disabled = false;
break;
case WebSocket.CLOSING:
stateLabel.innerHTML = "Closing...";
disable();
break;
case WebSocket.CONNECTING:
stateLabel.innerHTML = "Connecting...";
disable();
break;
case WebSocket.OPEN:
stateLabel.innerHTML = "Open";
enable();
break;
default:
stateLabel.innerHTML = "Unknown WebSocket State: " + htmlEscape(socket.readyState);
disable();
break;
}
}
}
closeButton.onclick = function () {
if (!socket || socket.readyState !== WebSocket.OPEN) {
alert("socket not connected");
}
socket.close(1000, "Closing from client");
};
sendButton.onclick = function () {
if (!socket || socket.readyState !== WebSocket.OPEN) {
alert("socket not connected");
}
var data = sendMessage.value;
socket.send(data);
commsLog.innerHTML += '<tr>' +
'<td class="commslog-client">Client</td>' +
'<td class="commslog-server">Server</td>' +
'<td class="commslog-data">' + htmlEscape(data) + '</td></tr>';
};
connectButton.onclick = function() {
stateLabel.innerHTML = "Connecting";
socket = new WebSocket(connectionUrl.value);
socket.onopen = function (event) {
updateState();
commsLog.innerHTML += '<tr>' +
'<td colspan="3" class="commslog-data">Connection opened</td>' +
'</tr>';
};
socket.onclose = function (event) {
updateState();
commsLog.innerHTML += '<tr>' +
'<td colspan="3" class="commslog-data">Connection closed. Code: ' + htmlEscape(event.code) + '. Reason: ' + htmlEscape(event.reason) + '</td>' +
'</tr>';
};
socket.onerror = updateState;
socket.onmessage = function (event) {
commsLog.innerHTML += '<tr>' +
'<td class="commslog-server">Server</td>' +
'<td class="commslog-client">Client</td>' +
'<td class="commslog-data">' + htmlEscape(event.data) + '</td></tr>';
};
};
function htmlEscape(str) {
return str.toString()
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,176 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style>
table {
border: 0
}
.commslog-data {
font-family: Consolas, Courier New, Courier, monospace;
}
.commslog-server {
background-color: red;
color: white
}
.commslog-client {
background-color: green;
color: white
}
</style>
</head>
<body>
<h1>DataStreams via WebSocket</h1>
<p id="stateLabel">Ready to connect...</p>
<div>
<label for="connectionUrl">WebSocket Server URL:</label>
<input id="connectionUrl" />
<button id="connectButton" type="submit">Connect</button>
</div>
<p></p>
<div>
<label for="sendMessage">Query:</label>
<input id="sendMessage" disabled />
<button id="sendButton" type="submit" disabled>Send</button>
<button id="closeButton" disabled>Close Socket</button>
</div>
<h2>Communication Log</h2>
<table style="width: 800px">
<thead>
<tr>
<td style="width: 100px">From</td>
<td style="width: 100px">To</td>
<td>Data</td>
</tr>
</thead>
<tbody id="commsLog">
</tbody>
</table>
<script>
var connectionUrl = document.getElementById("connectionUrl");
var connectButton = document.getElementById("connectButton");
var stateLabel = document.getElementById("stateLabel");
var sendMessage = document.getElementById("sendMessage");
var sendButton = document.getElementById("sendButton");
var commsLog = document.getElementById("commsLog");
var closeButton = document.getElementById("closeButton");
var socket;
var scheme = document.location.protocol === "https:" ? "wss" : "ws";
var port = document.location.port ? (":" + document.location.port) : "";
connectionUrl.value = scheme + "://" + document.location.hostname + port + "/ws" ;
function updateState() {
function disable() {
sendMessage.disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
function enable() {
sendMessage.disabled = false;
sendButton.disabled = false;
closeButton.disabled = false;
}
connectionUrl.disabled = true;
connectButton.disabled = true;
if (!socket) {
disable();
} else {
switch (socket.readyState) {
case WebSocket.CLOSED:
stateLabel.innerHTML = "Closed";
disable();
connectionUrl.disabled = false;
connectButton.disabled = false;
break;
case WebSocket.CLOSING:
stateLabel.innerHTML = "Closing...";
disable();
break;
case WebSocket.CONNECTING:
stateLabel.innerHTML = "Connecting...";
disable();
break;
case WebSocket.OPEN:
stateLabel.innerHTML = "Open";
enable();
break;
default:
stateLabel.innerHTML = "Unknown WebSocket State: " + htmlEscape(socket.readyState);
disable();
break;
}
}
}
closeButton.onclick = function () {
if (!socket || socket.readyState !== WebSocket.OPEN) {
alert("socket not connected");
}
socket.close(1000, "Closing from client");
};
sendButton.onclick = function () {
if (!socket || socket.readyState !== WebSocket.OPEN) {
alert("socket not connected");
}
const rpcReq = {
jsonrpc: "2.0",
method: "subscribe",
params: {
query: sendMessage.value,
},
id: 1,
};
const rpcReqStr = JSON.stringify(rpcReq);
socket.send(rpcReqStr);
commsLog.innerHTML += '<tr>' +
'<td class="commslog-client">Client</td>' +
'<td class="commslog-server">Server</td>' +
'<td class="commslog-data">' + htmlEscape(rpcReqStr) + '</td></tr>';
};
connectButton.onclick = function() {
stateLabel.innerHTML = "Connecting";
socket = new WebSocket(connectionUrl.value);
socket.onopen = function (event) {
updateState();
commsLog.innerHTML += '<tr>' +
'<td colspan="3" class="commslog-data">Connection opened</td>' +
'</tr>';
};
socket.onclose = function (event) {
updateState();
commsLog.innerHTML += '<tr>' +
'<td colspan="3" class="commslog-data">Connection closed. Code: ' + htmlEscape(event.code) + '. Reason: ' + htmlEscape(event.reason) + '</td>' +
'</tr>';
};
socket.onerror = updateState;
socket.onmessage = function (event) {
commsLog.innerHTML += '<tr>' +
'<td class="commslog-server">Server</td>' +
'<td class="commslog-client">Client</td>' +
'<td class="commslog-data">' + htmlEscape(event.data) + '</td></tr>';
};
};
function htmlEscape(str) {
return str.toString()
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
</script>
</body>
</html>
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
</ItemGroup>
</Project>
@@ -0,0 +1,59 @@
namespace Bts.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using System.Diagnostics;
public static class HostBuilderExtensions {
public static IHostBuilder CreateBtsBuilder(string[] args) {
// Obtain calling assembly name here, rather than in the lambda where it is actually needed,
// because want the name of the assembly that called this function, not the assembly that will eventually invoke
// the lambda...that will be the Microsoft.Extensions.Hosting assembly.
// Get the name of the assembly that called this function. That will act as the "appsettings"
// json file prefix
var exeName = GetCallingAssemblyName();
return Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, configBuilder) =>
ConfigureApp(configBuilder, context.HostingEnvironment.EnvironmentName, exeName));
}
public static void ConfigureApp(IConfigurationBuilder configBuilder, string? environment = null, string? prefix = null) {
var baseFileName = prefix is null ? "appsettings" : $"{prefix}.appsettings";
AddJsonFile($"{baseFileName}.json");
if (environment is not null) {
AddJsonFile($"{baseFileName}.{environment}.json");
}
var cfg = configBuilder.Build().GetSection("config")?.Value;
if (!SetBasePath(cfg)) {
AddJsonFile(cfg);
}
bool SetBasePath(string? path) {
if (!Directory.Exists(path)) {
return false;
}
configBuilder.SetBasePath(path);
return true;
}
bool AddJsonFile(string? path) {
if (!File.Exists(path)) {
return false;
}
configBuilder.AddJsonFile(path);
return true;
}
}
public static string GetCallingAssemblyName() =>
new StackTrace()
.GetFrame(2)
?.GetMethod()
?.Module
?.Assembly
?.GetName()
?.Name
?? throw new InvalidOperationException("Failed to determine name of calling assembly (calling method is null)");
}
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -0,0 +1,28 @@
namespace Bts.Net;
using System.Linq;
using System.Net;
/// <summary>
/// Provides facilities to compare hosts for equality.
/// <para>
/// This class doesn't implement IEqualityComparer because a consistent hash code cannot be
/// guarenteed for equivalent host strings. The equals impl relies on Dns.GetHostAddresses, which
/// is non-determinstic (implementations that depends on mutable properties face a similar problem).
/// </para>
/// </summary>
public class HostComparer {
public static bool Equals(string? x, string? y) {
if (x is null) {
return y is null;
}
if (y is null) {
return false;
}
return Dns.GetHostAddresses(x).Intersect(
Dns.GetHostAddresses(y), IpAddressComparer.Instance).Any();
}
public static bool Equals(IPHostEntry x, IPHostEntry y) =>
x.AddressList.Intersect(y.AddressList, IpAddressComparer.Instance).Any();
}
@@ -0,0 +1,30 @@
namespace Bts.Net;
using System.Diagnostics.CodeAnalysis;
using System.Net;
/// <summary>
/// Compares ip addresses. Unlike the default comparer, v4 and equivalent v6 addresses are
/// considered equal.
/// </summary>
public class IpAddressComparer : IEqualityComparer<IPAddress> {
public static IEqualityComparer<IPAddress> Instance { get; } = new IpAddressComparer();
public bool Equals(IPAddress? x, IPAddress? y) {
if (x is null) {
return y is null;
}
if (y is null) {
return false;
}
return x.Equals(y) ||
(x.IsIPv4MappedToIPv6 && x.MapToIPv4().Equals(y)) ||
(y.IsIPv4MappedToIPv6 && y.MapToIPv4().Equals(x));
}
public int GetHashCode([DisallowNull] IPAddress address) =>
address.IsIPv4MappedToIPv6
// Return consistent hash code for v4 and equivalent v6
? address.MapToIPv4().GetHashCode()
: address.GetHashCode();
}
@@ -0,0 +1,33 @@
namespace Bts.Net;
using System;
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;
public class IpHostEntryJsonConverter : JsonConverter<IPHostEntry> {
public override IPHostEntry? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, IPHostEntry value, JsonSerializerOptions options) {
writer.WriteStartObject();
// HostName
writer.WriteString(nameof(value.HostName), value.HostName);
// Aliases
writer.WritePropertyName("Aliases");
writer.WriteStartArray();
foreach (var alias in value.Aliases) {
writer.WriteStringValue(alias);
}
writer.WriteEndArray();
// Addresses
writer.WritePropertyName("Addresses");
writer.WriteStartArray();
foreach (var address in value.AddressList) {
writer.WriteStringValue(address.ToString());
}
writer.WriteEndArray();
writer.WriteEndObject();
}
}
@@ -0,0 +1,39 @@
namespace Bts.Net;
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Text.Json;
/// <summary>
/// This class seriailzes IpHostEntry to json (string). A common buffer is used to reduce memory
/// allocations on repeated calls to <see cref="Serialize"/>. For that reason, this class is NOT
/// thread safe.
/// </summary>
public class IpHostEntryJsonSerializer {
readonly MemoryStream buffer = new();
readonly IpHostEntryJsonConverter converter;
public IpHostEntryJsonSerializer(IpHostEntryJsonConverter? converter = null) =>
this.converter = converter ?? new() { };
public string? Serialize(IPHostEntry dp, JsonSerializerOptions? opts = null) {
opts ??= JsonSerializerOptions.Default;
buffer.Position = 0;
buffer.SetLength(0);
using (var jsonWriter = new Utf8JsonWriter(buffer)) {
converter.Write(jsonWriter, dp, opts);
}
var bytes = buffer.GetBuffer();
var span = new ArraySegment<byte>(bytes, 0, (int)buffer.Length);
return Encoding.UTF8.GetString(span);
}
public void Serialize(IPHostEntry dp, Utf8JsonWriter jsonWriter, JsonSerializerOptions? opts = null) {
opts ??= JsonSerializerOptions.Default;
buffer.Position = 0;
buffer.SetLength(0);
converter.Write(jsonWriter, dp, opts);
}
}
+94
View File
@@ -0,0 +1,94 @@
namespace Bts.Net;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Text.RegularExpressions;
public static partial class IpUtils {
static readonly Regex ipv4Regex = new(string.Join('.', @"(((0|1)\d)|(2\d)|\d)?\d"));
// public static void ParseUri(string str, out string? scheme, out string? host, out string? port, out string? path, out string? queryParams) {
// if (Uri.TryCreate(str, UriKind.Absolute, out var uri)) {
// }
// }
// public static bool AreEndpointsEqual(Uri? x, Uri? y) {
// if (x is null) {
// return y is null;
// }
// if (y is null) {
// return false;
// }
// if (x.IsAbsoluteUri != y.IsAbsoluteUri) {
// return false;
// }
// if (x.IsAbsoluteUri) {
// if (x.Scheme != y.Scheme) {
// return false;
// }
// }
// string? xHost = null;
// }
// static string ResolveHostIpv4(Uri uri) {
// // switch (uri.HostNameType) {
// // case UriHostNameType.IPv4:
// // return uri.Host;
// // case UriHostNameType.Dns:
// // break;
// // case UriHostNameType.IPv6:
// // var addresses = Dns.GetHostAddresses(uri.Host);
// // foreach (var address in addresses) {
// // if (address.IsIPv4MappedToIPv6) {
// // return address.MapToIPv4().Address;
// // }
// // }
// // break;
// // }
// var hostAddresses = Dns.GetHostAddresses(uri.Host);
// foreach (IPAddress address in hostAddresses) {
// if (address.Equals(remoteEndPoint.Address) ||
// (address.IsIPv4MappedToIPv6 && address.MapToIPv4().Equals(remoteEndPoint.Address)) ||
// (remoteEndPoint.Address.IsIPv4MappedToIPv6 && remoteEndPoint.Address.MapToIPv4().Equals(address))) {
// isMatch = true;
// break;
// }
// }
// }
// public static bool AreEndpointsEqual(string? x, string? y) {
// if (x == y) {
// return true;
// }
// if (x is null) {
// return y is null;
// }
// if (y is null) {
// return false;
// }
// var xUri = new Uri(x);
// var yUri = new Uri(y);
// // return xUri.Equals(yUri);
// }
public static bool IsIpv4(string host) {
host = removeSchemaFromUrl(host);
host = removePortFromUrl(host);
return ipv4Regex.IsMatch(host);
}
static string removeSchemaFromUrl(string url) {
var i = url.IndexOf("://");
return i < 0 ? url : url.Substring(i + 3);
}
static string removePortFromUrl(string url) {
var match = MyRegex().Match(url);
return match.Success
? string.Concat(url.AsSpan(0, match.Index), url.AsSpan(match.Index + match.Length))
: url;
}
[GeneratedRegex(":\\d{1,4}")]
private static partial Regex MyRegex();
}
@@ -7,14 +7,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.24.3" />
<PackageReference Include="YaccLexTools" Version="1.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DataStreamCore\DataStreamCore.csproj" />
<ProjectReference Include="..\ExpressionCore\ExpressionCore.csproj" />
<ProjectReference Include="..\..\client\spark_core\spark_core.csproj" />
</ItemGroup>
<ItemGroup Label="ColumnMapExpression">
@@ -14,14 +14,11 @@ namespace BCG {
myVS = vs;
}
public CountStream(string tableName)
: this(new TableStream(tableName)) { }
protected override void myOnFirstSubscriber() {
myVS.onChange += new ChangeDelegate(myHandleChanges);
myVS.onChange += myHandleChanges;
}
protected override void myOnLastSubscriber() {
myVS.onChange -= new ChangeDelegate(myHandleChanges);
myVS.onChange -= myHandleChanges;
}
@@ -78,11 +78,12 @@ namespace bts.BlueOrb {
#endregion
#region overridden methods
protected override void myOnFirstSubscriber() { myParentStream.onChange += new ChangeDelegate(myHandleCallbacks); }
protected override void myOnFirstSubscriber() =>
myParentStream.onChange += myHandleCallbacks;
protected override void myOnLastSubscriber() {
myLastFiredRows = null;
myParentStream.onChange -= new ChangeDelegate(myHandleCallbacks);
myParentStream.onChange -= myHandleCallbacks;
}
protected override void myFireEvent(object o) {
@@ -16,10 +16,10 @@ namespace bts.BlueOrb {
}
protected override void myOnFirstSubscriber() {
_stream.onChange += new ChangeDelegate(_onstream_change);
_stream.onChange += _onstream_change;
}
protected override void myOnLastSubscriber() {
_stream.onChange -= new ChangeDelegate(_onstream_change);
_stream.onChange -= _onstream_change;
}
@@ -12,12 +12,11 @@ using Spark.Core.Disposables;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
public static class DataStreamExtensions {
public static ViewStream AsViewStream(this IObservable<DataPacket> source) =>
public static ViewStream AsViewStream(this IObservable<IDataPacket> source) =>
source as ViewStream ?? new ObservableViewStream(source);
public static ViewStream AsViewStream<T>(this IObservable<IDataPacket<T>> source, string? name = null, ILogger? logger = null) where T : IBlueRow =>
@@ -89,6 +88,25 @@ public static class DataStreamExtensions {
.Where(t => t.Dp.addedRows.Any() || t.Dp.deletedRows.Any())
.Select(t => t.Dp);
public static IObservable<IDataPacket<BlueRowAdapter<T>>> AsDataPackets<T, TKey>(this IObservable<TableUpdate<T, TKey>> source, BlueTableSchemaAdapter<T> schema) where TKey : IEquatable<TKey> {
return Observable.Create<IDataPacket<BlueRowAdapter<T>>>(Subscribe);
IDisposable Subscribe(IObserver<IDataPacket<BlueRowAdapter<T>>> o) {
var adapter = new BlueTableImmutableAdapter<T>(schema);
IDataPacket<BlueRowAdapter<T>> initDataPacket = DataPacket<BlueRowAdapter<T>>.Empty(schema);
return source.Scan((Adapter: adapter, DataPacket: initDataPacket), OnUpdate).Select(res => res.DataPacket).Subscribe(o);
(BlueTableImmutableAdapter<T> Adapter, IDataPacket<BlueRowAdapter<T>> DataPacket) OnUpdate(
(BlueTableImmutableAdapter<T> Adapter, IDataPacket<BlueRowAdapter<T>> DataPacket) state, TableUpdate<T, TKey> tableUpdate) {
var bd = state.Adapter.ToDataPacketBuilder();
bd.Update(tableUpdate);
var dp = bd.ToDataPacket();
var tableAdapter = bd.ToImmutable();
return (bd.ToImmutable(), dp);
}
}
}
public static ViewStream AsViewStream<T>(this IObservable<TableUpdate<T, int>> source, IBlueTableSchema schema) where T : IBlueRow =>
source.AsDataPackets(schema).AsViewStream();
@@ -3,24 +3,24 @@ namespace bts.SilverPine {
using BCG;
using System.Collections.Generic;
using Bts.DataStreamExpressions;
using Bts.DataStreamCore;
// All this does is take in a dsl expression, parse it and fire the data that come out of running it.
// So for example, if an expresison is passed
[DataStreamUserRespresentation("dsl")]
public class DSLStream : ViewStream {
private readonly string _dsl;
public DSLStream(string dsl) {
public DSLStream(string dsl, IDataStreamFactory streamFactory) {
Log.Gen.info($"DSLStream.cons dsl: {dsl}");
_dsl = dsl;
ViewStream rs = build_stream(dsl);
rs = rs ?? new LiteralStream("error", $"msg:invalid dsl \"{dsl}\"");
var rs = build_stream(streamFactory, dsl) ?? new LiteralStream("error", $"msg:invalid dsl");
mySetRealSource(rs);
}
static ViewStream? build_stream(string stream_expression) {
static ViewStream? build_stream(IDataStreamFactory streamFactory, string stream_expression) {
try {
var exp = DataStreamExpressionsInterpreter.Evaluate(stream_expression);
var rv = exp.getView(new Dictionary<string, StatementExpression>(0));
var rv = exp.getView(new Dictionary<string, StatementExpression>(0), streamFactory);
return rv;
} catch (Exception ex) {
@@ -29,4 +29,4 @@ namespace bts.SilverPine {
}
}
}
}
}
@@ -8,13 +8,13 @@ using Microsoft.Extensions.Logging;
using System;
public class ObservableViewStream : ViewStream, IObservable<DataPacket>, IDisposable {
readonly IObservable<DataPacket> source;
public class ObservableViewStream : ViewStream, IObservable<IDataPacket>, IDisposable {
readonly IObservable<IDataPacket> source;
readonly string? name;
readonly ILogger? logger;
IDisposable? subscription;
public ObservableViewStream(IObservable<DataPacket> source, string? name = null, ILogger? logger = null) {
public ObservableViewStream(IObservable<IDataPacket> source, string? name = null, ILogger? logger = null) {
this.source = source;
this.name = name;
this.logger = logger;
@@ -45,6 +45,6 @@ public class ObservableViewStream : ViewStream, IObservable<DataPacket>, IDispos
subscription = null;
}
IDisposable IObservable<DataPacket>.Subscribe(IObserver<DataPacket> observer) =>
IDisposable IObservable<IDataPacket>.Subscribe(IObserver<IDataPacket> observer) =>
source.Subscribe(observer);
}
@@ -1,90 +0,0 @@
using System;
using System.Collections.Generic;
using Bts.DataStreamExpressions;
using BCG.Utilities.General;
using bts;
namespace BCG {
// listens to the requests posted on "cloud_streams" request table, and
// if unfulfilled starts a ViewPubsliher
public class RemoteQueryManager : IWorker {
public class StreamPubUnit {
public string name;
public string stream_expression;
public ViewStream stream;
public bool persist;
public ViewPublisher view_publisher;
public StreamPubUnit(string name_, string stream_expression_, ViewStream stream_, bool persist_, ViewPublisher view_publisher_) {
name = name_;
stream_expression = stream_expression_;
stream = stream_;
persist = persist_;
view_publisher = view_publisher_;
}
}
static ViewStream? build_stream(string stream_expression)
{
try
{
var exp = DataStreamExpressionsInterpreter.Evaluate(stream_expression);
var rv = exp.getView(new Dictionary<string, StatementExpression>(0));
return rv;
} catch (Exception ex) {
bts.Log.assert(false, $"Could not build stream: {stream_expression} ex: {ex}");
return null;
}
}
private IDictionary<string, StreamPubUnit>
_stream_pub_map = new Dictionary<string, StreamPubUnit>();
private ViewStream _stream = new TableStream("cloud_streams");
private PubTableBroker _broker = new PubTableBroker();
private ManagedSubscriber _sub = ManagedSubscriber.instance;
public RemoteQueryManager() {
_stream.onChange += _on_str;
_sub.subscribe("RemoteRequest", (string tag, byte[] data) => {
RemoteRequest req = RemoteRequest.construct_from_bytes(data);
_spawn_remote_stream(req.published_table_name, req.stream_expression, false);
});
}
private void _on_str(object obj)
{
if (obj is not DataPacket dp) {
return;
}
foreach (IBlueRow r in dp.newRows)
{
string name = r.get<string>("name");
bool persist = r.get<bool>("persist");
string stream_exp = r.get<string>("stream_exp");
if (!_stream_pub_map.ContainsKey(name)) {
_spawn_remote_stream(name, stream_exp, persist);
}
}
}
private void _spawn_remote_stream(string name, string stream_expression, bool persist) {
bts.Log.Gen.info($"CloudStreamManager.assign name: {name} stream_expression: {stream_expression}");
ViewStream stream = build_stream(stream_expression);
_expose_to_ws(name, stream);
if (persist) {
stream = new PersistorStream(stream, name, "C:\\Temp\\", 1000000); // 10MBs
}
ViewPublisher view_pub = new ViewPublisher(name, _broker, stream);
StreamPubUnit u = new StreamPubUnit(name, stream_expression, stream, persist, view_pub);
_stream_pub_map.Add(name, u);
}
private void _expose_to_ws(string name, ViewStream stream) {
// TODO: john
}
public void doWork() {
}
}
}
@@ -0,0 +1,131 @@
using Bts.DataStream;
using Bts.DataStreamCore;
using Bts.DataStreamExpressions;
using Bts.PubSub;
using bts;
using Microsoft.Extensions.Hosting;
using Spark.Core.Disposables;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace BCG {
// listens to the requests posted on "cloud_streams" request table, and
// if unfulfilled starts a ViewPubsliher
public class RemoteQueryManager : IHostedService {
public class StreamPubUnit {
public string name;
public string stream_expression;
public ViewStream stream;
public bool persist;
public ViewPublisher view_publisher;
public StreamPubUnit(string name_, string stream_expression_, ViewStream stream_, bool persist_, ViewPublisher view_publisher_) {
name = name_;
stream_expression = stream_expression_;
stream = stream_;
persist = persist_;
view_publisher = view_publisher_;
}
}
static ViewStream? build_stream(IDataStreamFactory streamFactory, string stream_expression)
{
try
{
var exp = DataStreamExpressionsInterpreter.Evaluate(stream_expression);
var rv = exp.getView(new Dictionary<string, StatementExpression>(0), streamFactory);
return rv;
} catch (Exception ex) {
bts.Log.assert(false, $"Could not build stream: {stream_expression} ex: {ex}");
return null;
}
}
private IDictionary<string, StreamPubUnit>
_stream_pub_map = new Dictionary<string, StreamPubUnit>();
readonly IPubTableBroker _broker;
readonly IManagedSubscriber _sub;
readonly IDataStreamFactory streamFactory;
readonly object syncRoot = new();
IDisposable? cloudStreamsSub;
IDisposable? remoteRequestsSub;
bool isDisposed;
public RemoteQueryManager(IManagedSubscriber managedSubscriber, IPubTableBroker pubTableBroker, IEnumerable<IDataStreamFactory> streamFactories) {
_sub = managedSubscriber;
_broker = pubTableBroker;
var streamFactoryList = streamFactories.ToList();
streamFactory = streamFactoryList.Count == 1
? streamFactoryList[0]
: new CompositeDataStreamFactory(streamFactoryList);
}
public Task StartAsync(CancellationToken cancellationToken) {
lock (syncRoot) {
Log.assert(!isDisposed);
Log.isnull(cloudStreamsSub);
Log.isnull(remoteRequestsSub);
var cloudStream = streamFactory.Create("table", new (string?, int?, object?)[] { ("name", 0, "cloud_streams") })
?? throw new Exception("Failed to create TableStream 'cloud_stream'");
cloudStreamsSub = cloudStream.AsObservable().Subscribe(_on_str);
remoteRequestsSub = _sub.Observe("RemoteRequest").Subscribe(data => {
RemoteRequest req = RemoteRequest.construct_from_bytes(data);
_spawn_remote_stream(req.published_table_name, req.stream_expression, false);
});
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) {
lock (syncRoot) {
if (!isDisposed) {
isDisposed = true;
Disposable.dispose(ref cloudStreamsSub);
Disposable.dispose(ref remoteRequestsSub);
}
}
return Task.CompletedTask;
}
private void _on_str(DataPacket dp)
{
foreach (IBlueRow r in dp.newRows)
{
string name = r.get<string>("name");
bool persist = r.get<bool>("persist");
string stream_exp = r.get<string>("stream_exp");
if (!_stream_pub_map.ContainsKey(name)) {
_spawn_remote_stream(name, stream_exp, persist);
}
}
}
private void _spawn_remote_stream(string name, string stream_expression, bool persist) {
bts.Log.Gen.info($"CloudStreamManager.assign name: {name} stream_expression: {stream_expression}");
var stream = build_stream(streamFactory, stream_expression);
if (stream is null) {
Log.DataFlow.error($"Failed to spawn remote stream. name: {name}; expression: {stream_expression}");
return;
}
_expose_to_ws(name, stream);
if (persist) {
stream = new PersistorStream(stream, name, "C:\\Temp\\", 1000000); // 10MBs
}
ViewPublisher view_pub = new ViewPublisher(name, _broker, stream);
StreamPubUnit u = new StreamPubUnit(name, stream_expression, stream, persist, view_pub);
_stream_pub_map.Add(name, u);
}
private void _expose_to_ws(string name, ViewStream stream) {
// TODO: john
}
}
}
@@ -2,17 +2,29 @@ using BCG;
using bts;
using Bts.DataStreamCore.StreamFactories;
using Spark.Core.Disposables;
using System;
namespace BCG {
// this is a stream so we can run it from the DSQA (since dsqa can only load streams)
[DataStreamUserRespresentation("remoteQuery")]
/// <summary>
/// This is a stream so we can run it from the DSQA (since dsqa can only load streams).
/// <para>
/// Note: It's hidden because this stream is now created by an IDataStreamFactory
/// (see RemoteQueryStreamFactory.cs for details)
/// </para>
/// </summary>
[DataStreamUserRespresentation(false)]
public class RemoteQueryStream : ViewStream {
private readonly string _name, _stream_exp;
readonly TableStreamFactory tableStreamFactory;
private PublisherCore _publisher_core = new PublisherCore();
private ViewStream _source_str;
IDisposable? subscription;
public RemoteQueryStream(params string[] lines) {
public RemoteQueryStream(TableStreamFactory tableStreamFactory, params string[] lines) {
this.tableStreamFactory = tableStreamFactory;
_stream_exp = get_expression(lines);
_name = Guid.NewGuid().ToString();
_name = $"remotereq_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString()}";
@@ -21,17 +33,17 @@ namespace BCG {
}
protected override void myOnFirstSubscriber() {
Log.isnull(subscription);
RemoteRequest rem_req = new RemoteRequest(_stream_exp, _name);
byte[] rem_req_bytes = rem_req.serialize();
_publisher_core.publish("System", "RemoteRequest", rem_req_bytes);
_source_str = new TableStream(_name);
_source_str.onChange += new ChangeDelegate(_handle_data);
var sourceStream = tableStreamFactory.Create(_name);
sourceStream.onChange += _handle_data;
subscription = Disposable.Create(() => sourceStream.onChange -= _handle_data);
}
protected override void myOnLastSubscriber() {
//maybe there should be a way to turn remote requests off ?
_source_str.onChange -= new ChangeDelegate(_handle_data);
}
protected override void myOnLastSubscriber() =>
Disposable.dispose(ref subscription);
static string get_expression(params string[] lines) {
for (int i = 0; i < lines.Length; i++) {
@@ -2,7 +2,7 @@ using System;
namespace bts.BlueOrb {
using BCG;
[DataStreamUserRespresentation("CountStream")]
[DataStreamUserRespresentation("oldCount")]
public class CountStream : ViewStream {
private DataPacket myLastFiredDataPacket;
private IBlueTableSchema mySchema = new BlueTableSchema("count",
@@ -15,16 +15,11 @@ namespace bts.BlueOrb {
myVS = vs;
}
public CountStream(string tableName)
: this(new TableStream(tableName)) { }
protected override void myOnFirstSubscriber() {
myVS.onChange += new ChangeDelegate(myHandleChanges);
}
protected override void myOnLastSubscriber() {
myVS.onChange -= new ChangeDelegate(myHandleChanges);
}
protected override void myOnFirstSubscriber () =>
myVS.onChange += myHandleChanges;
protected override void myOnLastSubscriber () =>
myVS.onChange -= myHandleChanges;
private void myHandleChanges(object newValue) {
myFireEvent(
Loaded 100 of 677 files, more files were not shown because too many files have changed in this diff. Show more