diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dc024f89..cc8891035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix: correctly remember reserved utxos and don't accidentally double spend - Feat: Allow recovering from a stuck protocol state by resending last outbound dlc message on connect - Feat: Allow continuing from an offered dlc channel state (offered, settle offered and collab close offered) +- Feat: add a new project `webapp`. Eventually this will have the same functionality as our app (and more) and can be run on a self-hosted server ## [1.7.4] - 2023-12-20 diff --git a/Cargo.lock b/Cargo.lock index 63db5d7ec..2f85370fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,14 +204,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", - "axum-core", + "axum-core 0.3.4", "base64 0.21.0", "bitflags 1.3.2", "bytes", "futures-util", - "http", - "http-body", - "hyper", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.24", "itoa", "matchit", "memchr", @@ -233,6 +233,40 @@ dependencies = [ "tower-service", ] +[[package]] +name = "axum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e" +dependencies = [ + "async-trait", + "axum-core 0.4.3", + "bytes", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.1.0", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "axum-core" version = "0.3.4" @@ -242,14 +276,35 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 0.2.9", + "http-body 0.4.5", "mime", "rustversion", "tower-layer", "tower-service", ] +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.67" @@ -714,7 +769,7 @@ version = "1.7.4" dependencies = [ "anyhow", "atty", - "axum", + "axum 0.6.20", "bdk", "bitcoin", "clap", @@ -1491,7 +1546,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.9", "indexmap 1.9.2", "slab", "tokio", @@ -1499,6 +1554,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991910e35c615d8cab86b5ab04be67e6ad24d2bf5f4f11fdbbed26da999bbeab" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 1.0.0", + "indexmap 2.0.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hash32" version = "0.2.1" @@ -1636,6 +1710,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.5" @@ -1643,10 +1728,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http", + "http 0.2.9", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.0.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +dependencies = [ + "bytes", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ce4ef31cda248bbdb6e6820603b82dfcd9e833db65a43e997a0ccec777d11fe" + [[package]] name = "httparse" version = "1.8.0" @@ -1675,27 +1789,46 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.21", + "http 0.2.9", + "http-body 0.4.5", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", "want", ] +[[package]] +name = "hyper" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.1", + "http 1.0.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "tokio", +] + [[package]] name = "hyper-timeout" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper", + "hyper 0.14.24", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -1708,12 +1841,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.24", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdea9aac0dbe5a9240d68cfd9501e2db94222c6dc06843e06640b9e07f0fdc67" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "hyper 1.1.0", + "pin-project-lite", + "socket2 0.5.5", + "tokio", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1864,9 +2015,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libm" @@ -2096,7 +2247,7 @@ dependencies = [ "async-stream", "async-trait", "atty", - "axum", + "axum 0.6.20", "bdk", "bitcoin", "bitmex-client", @@ -2186,9 +2337,19 @@ dependencies = [ [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] [[package]] name = "minimal-lexical" @@ -2254,7 +2415,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 0.2.9", "httparse", "log", "memchr", @@ -3005,10 +3166,10 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.21", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.24", "hyper-tls", "ipnet", "js-sys", @@ -3095,6 +3256,40 @@ dependencies = [ "rand", ] +[[package]] +name = "rust-embed" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.23", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rust_decimal" version = "1.29.0" @@ -3222,6 +3417,15 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.21" @@ -3539,6 +3743,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "socks" version = "0.3.4" @@ -3796,7 +4010,7 @@ dependencies = [ "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.4.9", "tokio-macros", "tracing", "windows-sys 0.48.0", @@ -3949,15 +4163,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", - "axum", + "axum 0.6.20", "base64 0.21.0", "bytes", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.21", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.24", "hyper-timeout", "percent-encoding", "pin-project", @@ -3990,6 +4204,31 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da193277a4e2c33e59e09b5861580c33dd0a637c3883d0fa74ba40c0374af2e" +dependencies = [ + "bitflags 2.4.1", + "bytes", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-layer" version = "0.3.2" @@ -4107,7 +4346,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.9", "httparse", "log", "native-tls", @@ -4130,6 +4369,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.10" @@ -4265,6 +4513,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" @@ -4376,6 +4634,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webapp" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum 0.7.4", + "mime_guess", + "rust-embed", + "tokio", + "tower", + "tower-http", + "tracing", + "tracing-subscriber", +] + [[package]] name = "webpki-roots" version = "0.23.1" diff --git a/Cargo.toml b/Cargo.toml index b929f1dfa..865750799 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["coordinator", "maker", "mobile/native", "crates/*"] +members = ["coordinator", "maker", "mobile/native", "crates/*", "webapp"] default-members = [ "coordinator", "maker", @@ -11,6 +11,7 @@ default-members = [ "crates/orderbook-client", "crates/trade", "crates/payout_curve", + "webapp", ] resolver = "2" diff --git a/webapp/.gitignore b/webapp/.gitignore new file mode 100644 index 000000000..4770a1ba7 --- /dev/null +++ b/webapp/.gitignore @@ -0,0 +1,112 @@ +# Created by https://www.toptal.com/developers/gitignore/api/flutter,dart,rust +# Edit at https://www.toptal.com/developers/gitignore?templates=flutter,dart,rust + +### Dart ### +# See https://www.dartlang.org/guides/libraries/private-files + +# Files and directories created by pub +.dart_tool/ +.packages +build/ +# If you're building an application, you may want to check-in your pubspec.lock +pubspec.lock + +# Directory created by dartdoc +# If you don't generate documentation locally you can remove this line. +doc/api/ + +# dotenv environment variables file +.env* + +# Avoid committing generated Javascript files: +*.dart.js +*.info.json # Produced by the --dump-info flag. +*.js # When generated by dart2js. Don't specify *.js if your + # project includes source files written in JavaScript. +*.js_ +*.js.deps +*.js.map + +.flutter-plugins +.flutter-plugins-dependencies + +### Dart Patch ### +# dotenv environment variables file +.env + +### Flutter ### +# Flutter/Dart/Pub related +**/doc/api/ +.fvm/flutter_sdk +.pub-cache/ +.pub/ +coverage/ +lib/generated_plugin_registrant.dart +# For library packages, don’t commit the pubspec.lock file. +# Regenerating the pubspec.lock file lets you test your package against the latest compatible versions of its dependencies. +# See https://dart.dev/guides/libraries/private-files#pubspeclock +#pubspec.lock + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/key.properties +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# End of https://www.toptal.com/developers/gitignore/api/flutter,dart,rust diff --git a/webapp/Cargo.toml b/webapp/Cargo.toml new file mode 100644 index 000000000..025a345b4 --- /dev/null +++ b/webapp/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "webapp" +version = "0.1.0" +build = "build.rs" +edition = "2021" + +[dependencies] +anyhow = "1" +axum = { version = "0.7", features = ["tracing"] } +mime_guess = "2.0.4" +rust-embed = "8.2.0" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +tower = { version = "0.4", features = ["util"] } +tower-http = { version = "0.5", features = ["fs", "trace"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/webapp/README.md b/webapp/README.md new file mode 100644 index 000000000..0d8421ff5 --- /dev/null +++ b/webapp/README.md @@ -0,0 +1,24 @@ +# A simple webfrontend to be self-hosted + +## Run frontend only in dev mode + +```bash +cd frontend +flutter run -d chrome +``` + +## Build the frontend to be served by rust + +```bash +flutter build web +``` + +## Run the rust app + +```bash +cargo run +``` + +The webinterface will be reachable under `http://localhost:3001` + +Note: if you can't see anything, you probably forgot to run `flutter build web` before diff --git a/webapp/build.rs b/webapp/build.rs new file mode 100644 index 000000000..931f1b6df --- /dev/null +++ b/webapp/build.rs @@ -0,0 +1,9 @@ +use std::fs; + +fn main() { + // ensure that the directory exists which needs to be embedded in our binary + let directory_path = "./frontend/build/web"; + if fs::create_dir_all(directory_path).is_err() { + std::process::exit(1); + } +} diff --git a/webapp/frontend/.gitignore b/webapp/frontend/.gitignore new file mode 100644 index 000000000..24476c5d1 --- /dev/null +++ b/webapp/frontend/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/webapp/frontend/.metadata b/webapp/frontend/.metadata new file mode 100644 index 000000000..8e2946d54 --- /dev/null +++ b/webapp/frontend/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "12fccda598477eddd19f93040a1dba24f915b9be" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 12fccda598477eddd19f93040a1dba24f915b9be + base_revision: 12fccda598477eddd19f93040a1dba24f915b9be + - platform: android + create_revision: 12fccda598477eddd19f93040a1dba24f915b9be + base_revision: 12fccda598477eddd19f93040a1dba24f915b9be + - platform: ios + create_revision: 12fccda598477eddd19f93040a1dba24f915b9be + base_revision: 12fccda598477eddd19f93040a1dba24f915b9be + - platform: linux + create_revision: 12fccda598477eddd19f93040a1dba24f915b9be + base_revision: 12fccda598477eddd19f93040a1dba24f915b9be + - platform: macos + create_revision: 12fccda598477eddd19f93040a1dba24f915b9be + base_revision: 12fccda598477eddd19f93040a1dba24f915b9be + - platform: web + create_revision: 12fccda598477eddd19f93040a1dba24f915b9be + base_revision: 12fccda598477eddd19f93040a1dba24f915b9be + - platform: windows + create_revision: 12fccda598477eddd19f93040a1dba24f915b9be + base_revision: 12fccda598477eddd19f93040a1dba24f915b9be + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/webapp/frontend/analysis_options.yaml b/webapp/frontend/analysis_options.yaml new file mode 100644 index 000000000..0d2902135 --- /dev/null +++ b/webapp/frontend/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/webapp/frontend/lib/main.dart b/webapp/frontend/lib/main.dart new file mode 100644 index 000000000..dda55548e --- /dev/null +++ b/webapp/frontend/lib/main.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // TRY THIS: Try running your application with "flutter run". You'll see + // the application has a blue toolbar. Then, without quitting the app, + // try changing the seedColor in the colorScheme below to Colors.green + // and then invoke "hot reload" (save your changes or press the "hot + // reload" button in a Flutter-supported IDE, or press "r" if you used + // the command line to start the app). + // + // Notice that the counter didn't reset back to zero; the application + // state is not lost during the reload. To reset the state, use hot + // restart instead. + // + // This works for code too, not just values: Most code changes can be + // tested with just a hot reload. + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + // This call to setState tells the Flutter framework that something has + // changed in this State, which causes it to rerun the build method below + // so that the display can reflect the updated values. If we changed + // _counter without calling setState(), then the build method would not be + // called again, and so nothing would appear to happen. + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // TRY THIS: Try changing the color here to a specific color (to + // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar + // change color while the other colors stay the same. + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: Column( + // Column is also a layout widget. It takes a list of children and + // arranges them vertically. By default, it sizes itself to fit its + // children horizontally, and tries to be as tall as its parent. + // + // Column has various properties to control how it sizes itself and + // how it positions its children. Here we use mainAxisAlignment to + // center the children vertically; the main axis here is the vertical + // axis because Columns are vertical (the cross axis would be + // horizontal). + // + // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" + // action in the IDE, or press "p" in the console), to see the + // wireframe for each widget. + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'You have pushed the button this many times:', + ), + Text( + '$_counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} diff --git a/webapp/frontend/pubspec.yaml b/webapp/frontend/pubspec.yaml new file mode 100644 index 000000000..93eb9f97f --- /dev/null +++ b/webapp/frontend/pubspec.yaml @@ -0,0 +1,90 @@ +name: frontend +description: A new Flutter project. +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=3.1.2 <4.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/webapp/frontend/test/widget_test.dart b/webapp/frontend/test/widget_test.dart new file mode 100644 index 000000000..812c97819 --- /dev/null +++ b/webapp/frontend/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:frontend/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/webapp/frontend/web/favicon.png b/webapp/frontend/web/favicon.png new file mode 100644 index 000000000..8aaa46ac1 Binary files /dev/null and b/webapp/frontend/web/favicon.png differ diff --git a/webapp/frontend/web/icons/Icon-192.png b/webapp/frontend/web/icons/Icon-192.png new file mode 100644 index 000000000..b749bfef0 Binary files /dev/null and b/webapp/frontend/web/icons/Icon-192.png differ diff --git a/webapp/frontend/web/icons/Icon-512.png b/webapp/frontend/web/icons/Icon-512.png new file mode 100644 index 000000000..88cfd48df Binary files /dev/null and b/webapp/frontend/web/icons/Icon-512.png differ diff --git a/webapp/frontend/web/icons/Icon-maskable-192.png b/webapp/frontend/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000..eb9b4d76e Binary files /dev/null and b/webapp/frontend/web/icons/Icon-maskable-192.png differ diff --git a/webapp/frontend/web/icons/Icon-maskable-512.png b/webapp/frontend/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000..d69c56691 Binary files /dev/null and b/webapp/frontend/web/icons/Icon-maskable-512.png differ diff --git a/webapp/frontend/web/index.html b/webapp/frontend/web/index.html new file mode 100644 index 000000000..fa4f09a73 --- /dev/null +++ b/webapp/frontend/web/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + frontend + + + + + + + + + + diff --git a/webapp/frontend/web/manifest.json b/webapp/frontend/web/manifest.json new file mode 100644 index 000000000..7bd4eaca7 --- /dev/null +++ b/webapp/frontend/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "frontend", + "short_name": "frontend", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/webapp/src/main.rs b/webapp/src/main.rs new file mode 100644 index 000000000..ab727b716 --- /dev/null +++ b/webapp/src/main.rs @@ -0,0 +1,128 @@ +use anyhow::Result; +use axum::http::header; +use axum::http::Request; +use axum::http::StatusCode; +use axum::http::Uri; +use axum::response::Html; +use axum::response::IntoResponse; +use axum::response::Response; +use axum::routing::get; +use axum::Router; +use rust_embed::RustEmbed; +use std::net::SocketAddr; +use std::time::Duration; +use tower_http::classify::ServerErrorsFailureClass; +use tower_http::trace::TraceLayer; +use tracing::Span; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::EnvFilter; + +#[tokio::main] +async fn main() -> Result<()> { + // TODO(bonomat): configure the logger properly + let filter = EnvFilter::new("").add_directive("debug".parse()?); + + tracing_subscriber::registry() + .with(filter) + .with(tracing_subscriber::fmt::layer()) + .init(); + + serve(using_serve_dir(), 3001).await?; + + Ok(()) +} + +fn using_serve_dir() -> Router { + Router::new() + .route("/", get(index_handler)) + .route("/main.dart.js", get(main_dart_handler)) + .route("/flutter.js", get(flutter_js)) + .route("/index.html", get(index_handler)) + .route("/assets/*file", get(static_handler)) + .fallback_service(get(not_found)) + .layer( + TraceLayer::new_for_http() + .on_request(|request: &Request, _span: &Span| { + tracing::debug!( + method = request.method().to_string(), + uri = request.uri().path(), + "request" + ) + }) + .on_response(()) + .on_body_chunk(()) + .on_eos(()) + .on_failure( + |error: ServerErrorsFailureClass, _latency: Duration, _span: &Span| { + tracing::error!("something went wrong : {error:#}") + }, + ), + ) +} + +// We use static route matchers ("/" and "/index.html") to serve our home +// page. +async fn index_handler() -> impl IntoResponse { + let result = "/index.html".parse::().expect("to be a valid uri"); + static_handler(result).await +} + +// We use static route matchers ("/main_dart.js") to serve our js library +async fn main_dart_handler() -> impl IntoResponse { + static_handler("/main.dart.js".parse::().expect("to be a valid uri")).await +} + +// We use static route matchers ("/flutter.js") to serve our js library +async fn flutter_js() -> impl IntoResponse { + static_handler("/flutter.js".parse::().expect("to be a valid uri")).await +} + +// We use a wildcard matcher ("/dist/*file") to match against everything +// within our defined assets directory. This is the directory on our Asset +// struct below, where folder = "examples/public/". +async fn static_handler(uri: Uri) -> impl IntoResponse { + let mut path = uri.path().trim_start_matches('/').to_string(); + + if path.starts_with("assets/") { + path = path.replace("assets/", ""); + } + + StaticFile(path) +} + +// Finally, we use a fallback route for anything that didn't match. +async fn not_found() -> Html<&'static str> { + Html("

404

Not Found

") +} + +#[derive(RustEmbed)] +#[folder = "frontend/build/web"] +struct Asset; + +pub struct StaticFile(pub T); + +impl IntoResponse for StaticFile +where + T: Into, +{ + fn into_response(self) -> Response { + let path = self.0.into(); + + match Asset::get(path.as_str()) { + Some(content) => { + let mime = mime_guess::from_path(path).first_or_octet_stream(); + ([(header::CONTENT_TYPE, mime.as_ref())], content.data).into_response() + } + None => (StatusCode::NOT_FOUND, "404 Not Found").into_response(), + } + } +} + +async fn serve(app: Router, port: u16) -> Result<()> { + let addr = SocketAddr::from(([0, 0, 0, 0], port)); + let listener = tokio::net::TcpListener::bind(addr).await?; + tracing::debug!("listening on {}", listener.local_addr()?); + axum::serve(listener, app).await?; + Ok(()) +}