diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a62fe1d..988e815 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,7 +23,7 @@ build-ui: - npm install - npm run build rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG artifacts: paths: - dist/ @@ -36,7 +36,7 @@ build-x64-bin: tags: - linux rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $CI_COMMIT_TAG script: - cargo build --release - cd target/release @@ -54,7 +54,7 @@ build-musl-bin: stage: build image: 'rust:latest' rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG script: - rustup target add x86_64-unknown-linux-musl - apt update && apt install -y musl-tools musl-dev @@ -74,7 +74,7 @@ build-arm-bin: stage: build image: 'rust:latest' rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $CI_COMMIT_TAG script: - rustup target add armv7-unknown-linux-gnueabihf - apt update @@ -96,7 +96,7 @@ build-win-bin: tags: - windows rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $CI_COMMIT_TAG script: - cargo build --release artifacts: @@ -113,7 +113,7 @@ deploy-dev-docker: - docker build -t $CI_REGISTRY/vade/vade-mecum . - docker push $CI_REGISTRY/vade/vade-mecum rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG deploy-binaries: dependencies: diff --git a/Cargo.lock b/Cargo.lock index 5e5ae7d..e82d945 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -652,6 +652,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -851,6 +862,22 @@ dependencies = [ "generic-array", ] +[[package]] +name = "clap" +version = "3.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62" +dependencies = [ + "atty", + "bitflags", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap", +] + [[package]] name = "const_fn" version = "0.4.9" @@ -1738,18 +1765,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "mio-named-pipes" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" -dependencies = [ - "log", - "mio 0.6.23", - "miow 0.3.7", - "winapi 0.3.9", -] - [[package]] name = "mio-uds" version = "0.6.8" @@ -1885,6 +1900,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", +] + [[package]] name = "ouroboros" version = "0.14.0" @@ -2872,6 +2896,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.86" @@ -2883,6 +2913,21 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" + [[package]] name = "thiserror" version = "1.0.30" @@ -3010,20 +3055,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" dependencies = [ "bytes 0.5.6", - "fnv", "futures-core", "iovec", "lazy_static", "libc", "memchr", "mio 0.6.23", - "mio-named-pipes", "mio-uds", - "num_cpus", "pin-project-lite 0.1.12", "signal-hook-registry", "slab", - "tokio-macros", "winapi 0.3.9", ] @@ -3042,14 +3083,15 @@ dependencies = [ "parking_lot", "pin-project-lite 0.2.8", "signal-hook-registry", + "tokio-macros", "winapi 0.3.9", ] [[package]] name = "tokio-macros" -version = "0.2.6" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote", @@ -3375,6 +3417,7 @@ dependencies = [ "base64", "bcrypt", "chrono", + "clap", "jemallocator", "jsonwebtoken", "mime_guess", @@ -3385,7 +3428,7 @@ dependencies = [ "serde", "serde_json", "sqlx", - "tokio 0.2.25", + "tokio 1.16.1", "tracing", "tracing-actix-web", "tracing-subscriber", diff --git a/Cargo.toml b/Cargo.toml index 84b3f66..5f0b02b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,10 +26,11 @@ bcrypt = "0.10.1" actix-web-httpauth = "0.6.0-beta.7" jsonwebtoken = "8.0.1" rand = "0.8.4" -tokio = { verison = "1", features=["full"] } +tokio = { version = "1.16.1", features=["full"] } base64 = "0.13.0" sqlx = { version = "^0.5", features=["sqlite", "migrate"] } reqwest = { version = "0.11.9", features = ["rustls-tls"], default-features=false } +clap = { version = "3.0.14", features=["cargo", "env"] } [target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.jemallocator] diff --git a/src/main.rs b/src/main.rs index 1475ba2..61cf9f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, path::Path}; use actix::SystemService; use actix_web::{get, web, App, HttpResponse, HttpServer}; +use clap::crate_version; use rust_embed::RustEmbed; use sea_orm::{prelude::*, Database}; use tokio::sync::Mutex; @@ -27,6 +28,29 @@ pub struct AppState { #[actix_rt::main] async fn main() { + let opts = clap::App::new("Vade Mecum") + .version(crate_version!()) + .arg( + clap::Arg::new("port") + .short('p') + .env("VADE_PORT") + .long("port") + .value_name("number") + .default_value("8089") + .help("Set the port for the HTTP server") + .takes_value(true), + ) + .arg( + clap::Arg::new("db") + .env("VADE_DB") + .long("db") + .value_name("path") + .default_value("./") + .help("Sets the path to the database location") + .takes_value(true), + ) + .get_matches(); + let subscriber = tracing_subscriber::registry().with( tracing_subscriber::fmt::Layer::new() .pretty() @@ -36,13 +60,18 @@ async fn main() { ); tracing::subscriber::set_global_default(subscriber).expect("Unable to set a global collector"); - let db = setup_database().await.unwrap(); + let db = setup_database(opts.value_of("db").unwrap_or_default()) + .await + .unwrap(); let state = web::Data::new(AppState { db, healthcheck_status: Mutex::new(HashMap::new()), }); - info!("Starting http server on 8080"); + info!( + "Starting http server on {}", + opts.value_of("port").unwrap_or_default() + ); let st = state.clone(); actix_rt::spawn(async move { @@ -79,6 +108,8 @@ async fn main() { } }); + let listen_host = format!("0.0.0.0:{}", opts.value_of("port").unwrap_or_default()); + HttpServer::new(move || { let cors = actix_cors::Cors::permissive(); App::new() @@ -89,7 +120,7 @@ async fn main() { .service(api::routes()) .service(dist) }) - .bind("0.0.0.0:8088") + .bind(listen_host) .unwrap() .run() .await @@ -118,10 +149,10 @@ async fn dist(path: web::Path) -> HttpResponse { } #[instrument] -async fn setup_database() -> error::Result { +async fn setup_database(db_path: &str) -> error::Result { let db_fname = "data.db"; - if !Path::new(db_fname).exists() { + if !Path::new(db_path).join(db_fname).exists() { std::fs::File::create(db_fname)?; } diff --git a/src/ui/components/ApplicationCategoryModal.vue b/src/ui/components/ApplicationCategoryModal.vue index c90d62e..82e3e51 100644 --- a/src/ui/components/ApplicationCategoryModal.vue +++ b/src/ui/components/ApplicationCategoryModal.vue @@ -2,14 +2,14 @@ @@ -39,6 +39,9 @@ export default { saveLabel() { return this.category.id ? "Update" : "Save"; }, + allowSave() { + return !!this.category.categoryName; + } }, methods: { close() { diff --git a/src/ui/components/ApplicationModal.vue b/src/ui/components/ApplicationModal.vue index 6979231..bb484c9 100644 --- a/src/ui/components/ApplicationModal.vue +++ b/src/ui/components/ApplicationModal.vue @@ -9,9 +9,9 @@ :items="selectCategories" emptyLabel="No Category" /> - + - + @@ -19,7 +19,7 @@ @@ -54,6 +54,9 @@ export default { selectCategories() { return this.categories.map((i) => ({ label: i.categoryName, value: i.id })); }, + saveAllowed() { + return (!!this.app.appName && !!this.app.url) + } }, methods: { close() { diff --git a/src/ui/components/BookmarkCategoryModal.vue b/src/ui/components/BookmarkCategoryModal.vue index e8d178a..e66da08 100644 --- a/src/ui/components/BookmarkCategoryModal.vue +++ b/src/ui/components/BookmarkCategoryModal.vue @@ -2,14 +2,14 @@ @@ -39,6 +39,9 @@ export default { saveLabel() { return this.category.id ? "Update" : "Save"; }, + allowSave() { + return this.category.categoryName + } }, methods: { close() { diff --git a/src/ui/components/BookmarkModal.vue b/src/ui/components/BookmarkModal.vue index 1ef63bc..115e1fd 100644 --- a/src/ui/components/BookmarkModal.vue +++ b/src/ui/components/BookmarkModal.vue @@ -8,14 +8,14 @@ :items="selectCategories" emptyLabel="No Category" /> - - + + @@ -48,6 +48,9 @@ export default { selectCategories() { return this.categories.map((i) => ({ label: i.categoryName, value: i.id })); }, + allowSave() { + return this.bookmark.bookmarkName && this.bookmark.url + } }, methods: { close() { diff --git a/src/ui/components/Button.vue b/src/ui/components/Button.vue index 4ad68da..3df2ed9 100644 --- a/src/ui/components/Button.vue +++ b/src/ui/components/Button.vue @@ -1,5 +1,5 @@ @@ -29,21 +30,18 @@ button:hover { box-shadow: rgba(0, 0, 0, 0.42) 0px 1px 3px, rgba(0, 0, 0, 0.64) 0px 1px 2px; } -button.danger { - background: #41141B; +button.danger {background: #41141B;} +button.danger:hover {background: #61141B;} + +button.primary {background: #21244B;} +button.primary:hover {background: #21246B;} + +button.disabled, button.disabled:hover, +button.primary.disabled, button.primary.disabled:hover, +button.danger.disabled, button.danger.disabled:hover { + color: #777; + box-shadow: none; + background-color: transparent; } - -button.danger:hover { - background: #61141B; -} - -button.primary { - background: #21244B; -} - - -button.primary:hover { - background: #21246B; -} diff --git a/src/ui/components/TextField.vue b/src/ui/components/TextField.vue index d962773..0f75b81 100644 --- a/src/ui/components/TextField.vue +++ b/src/ui/components/TextField.vue @@ -1,7 +1,7 @@ @@ -9,6 +9,7 @@ import FormField from "./FormField.vue"; export default { props: { + required: Boolean, label: String, modelValue: String, password: Boolean, @@ -17,8 +18,13 @@ export default { inputType() { return this.password ? "password" : "text"; }, + isError() { + return this.required && !this.modelValue + } + }, + components: { + FormField }, - components: { FormField }, methods: { handleInput() { this.$emit("update:modelValue", this.$refs.field.value); @@ -43,5 +49,14 @@ input { input:focus { transition: all 0.2s; border-bottom: 1px solid #336699; +} + input.error { + border-bottom: 1px solid #900; +} + input.error:focus { + border-bottom: 1px solid #c00; +} + label.error { + color: #eee; } diff --git a/src/ui/views/Dashboard.vue b/src/ui/views/Dashboard.vue index 4027bfe..8bd5bb2 100644 --- a/src/ui/views/Dashboard.vue +++ b/src/ui/views/Dashboard.vue @@ -298,7 +298,7 @@ h2 svg { cursor: pointer; font-size: 44px; font-weight: 900; - color: #252525; + color: rgba(255,255,255,0.035); text-shadow: 1px 1px rgba(0,0,0,0.15); position: absolute; bottom: 20px;